[Java] 열거형 enum

Published: by Creative Commons Licence

참고 문서

enum

enum은 자바 1.5부터 추가된 자료형이다.

enum ClassName {
    상수1, 상수2, ...
}

enum은 '일반적으로' 상수만을 갖는 타입이며, 흔히들 사용하는 static final String 상수와 비교했을 때 type-safety를 보장하는 장점이 있다.

enum City {
    서울, 인천, 부산
}

enum Color {
    RED, BLUE, GREEN, YELLOW
}

public class EnumTest {
    public static void main(String[] args) {
        City c1; // 열거형 변수 선언
        c1 = City.서울; // 열거형 상수 대입

        System.out.println(c1); // 서울
        System.out.println(City.인천); // 인천

        City c2 = City.대구; // compile error: 대구 cannot be resolved or is not a field
    }
}

사실은 클래스

enum은 편의 상 enum만을 위한 문법적 형식을 가질 뿐, 사실은 클래스다.

가령 아래처럼 선언된 enum은:

enum Animal {
    TIGGER, ZIBRA, SQUIRREL;
}

아래와 같은 의미이다:

class Animal {
    public static final Animal TIGGER = new Animal();
    public static final Animal ZIBRA = new Animal();
    public static final Animal SQUIRREL = new Animal();

    private Animal(){}
}

enum과 switch

enum은 내부적으로 int 타입으로 처리된다. 즉 switch문의 조건으로 사용할 수 있다:

enum Color {
    RED, BLUE, GREEN, YELLOW
    //1,   2,    3,      4
}

public class EnumTest {
    public static void main(String[] args) {
        Color c = Color.RED;

        switch (c) {
        case RED:
            System.out.println("빨간색 옷을 선택함"); break;
        case YELLOW:
            System.out.println("노란색 옷을 선택함"); break;
        }
    }
}

static 메서드와 non-static 메서드의 차이

static으로 선언한 메서드는 enum 자체의 클래스 메서드가 되고, non-static으로 선언한 메서드는 enum이 아니라 각 요소의 인스턴스 메서드가 된다. non-static의 경우 오버라이딩이 가능하다.

enum OS {
    WINDOWS, LINUX, UNIX,
    OSX {
        @Override
        public String getComment() {
            return "What are you looking at?";
        }
    };

    public String getComment() {
        return "Hello! I'm " + this.name() + "!";
    }

    public static OS valueOfIAESafe(String arg) {
        try {
            return OS.valueOf(arg);
        } catch (IllegalArgumentException e) {
            return null;
        }
    }
}

public class EnumTest {
    public static void main(String[] args) {
        System.out.println(OS.valueOfIAESafe("LINUX").getClass()); // class jdk.java.lang.OS
        System.out.println(OS.WINDOWS.getComment()); // Hello! I'm WINDOWS!
        System.out.println(OS.LINUX.getComment()); // Hello! I'm LINUX!
        System.out.println(OS.OSX.getComment()); // What are you looking at?
    }
}

name()과 toString()의 차이

enum Color {
    RED, BLUE, GREEN, YELLOW
}

public class EnumTest {
    public static void main(String[] args) {
        System.out.println(Color.RED); // RED
        System.out.println(Color.RED.toString()); // RED
        System.out.println(Color.RED.name()); // RED
    }
}

enum의 메서드인 name()toString()은 상수로 정의된 자기 자신의 이름을 그대로 반환한다. 참고로 enum의 추상 클래스인 Enum<Enum<E>>의 실제 소스는 아래와 같다:

public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {
    /**
     * The name of this enum constant, as declared in the enum declaration.
     * Most programmers should use the {@link #toString} method rather than
     * accessing this field.
     */
    private final String name;

    /**
     * Returns the name of this enum constant, exactly as declared in its
     * enum declaration.
     *
     * Most programmers should use the {@link #toString} method in
     * preference to this one, as the toString method may return
     * a more user-friendly name.  This method is designed primarily for
     * use in specialized situations where correctness depends on getting the
     * exact name, which will not vary from release to release.
     *
     * @return the name of this enum constant
     */
    public final String name() {
        return name;
    }

    /**
     * Returns the name of this enum constant, as contained in the
     * declaration.  This method may be overridden, though it typically
     * isn't necessary or desirable.  An enum type should override this
     * method when a more "programmer-friendly" string form exists.
     *
     * @return the name of this enum constant
     */
    public String toString() {
        return name;
    }

    // ... 생략
}

enum constructor

앞서 enum을 '일반적으로' 상수만을 갖는 타입이라 했지만 사실 보통의 클래스처럼 생성자와 메서드, 변수도 소유한다. 메서드와 변수는 특이하게도 상수를 경유한 접근방식을 사용하며 생성자는 오직 private 접근 제한자만 허용한다. 만약 private 외의 접근 제한자를 명시하면 컴파일 에러가 발생할 것이다.1

다음은 파라미터가 있는 생성자를 구현한 예시인데, 이 방법으로 상수를 단순 나열이나 비교하는 용도 외에 추가적인 의미를 지닌 값으로 활용할 수 있다. 가령 사과 종류별 가격이라던지…

소스 출처

enum Apple {
    A(10), B(9), C(12), D(15), E(8);

    private int price; // price of each apple

    // Constructor
    private Apple(int p) {
        price = p;
    }

    public int getPrice() {
        return price;
    }
}

public class EnumDemo3 {
    public static void main(String args[]) {
        // Display price of Winsap.
        System.out.println(Apple.A.getPrice());

        // Display all apples and prices.
        System.out.println("All apple prices:");
        for (Apple ele : Apple.values()) {
            System.out.println(ele + " costs " + ele.getPrice() + " cents.");
        }
    }
}

다음은 실적용 사례인 net.sf.uadetector.OperatingSystemFamily의 일부:

public enum OperatingSystemFamily {

    ANDROID("Android", Pattern.compile("Android")),

    // 생략

    /**
     * The internal family name in the UAS database.
     */
    @Nonnull
    private final String name;

    /**
     * The regular expression which a family name must be match.
     */
    @Nonnull
    private final Pattern pattern;

    private OperatingSystemFamily(@Nonnull final String name, @Nonnull final Pattern pattern) {
        this.name = name;
        this.pattern = pattern;
    }

    /**
     * Gets the internal family name in the UAS database.
     *
     * @return the internal family name
     */
    @Nonnull
    public String getName() {
        return this.name;
    }

    /**
     * Gets the regular expression which a family name must be match with.
     *
     * @return regular expression
     */
    @Nonnull
    public Pattern getPattern() {
        return pattern;
    }
}

enum의 파라미터로 클래스 찾기

만약 enum을 READ("R")처럼 어떠한 파라미터를 부여하며 선언했다면, 그 파라미터가 일치하는 클래스를 찾아 반환하는 메서드를 만들 수 있다.

TODO 뭔 소리야 설명이 이상해

public enum CrudMode {
    READ("R"), CREATE("C"), UPDATE("U"), DELETE("D");

    private final String label;

    CrudMode(String label) {
        this.label = label;
    }

    public String label() {
        return label;
    }

    /**
     * label로 enum 찾기
     *
     * @param label
     * @return
     */
    public static CrudMode of(String label) {
        CrudMode[] values = CrudMode.values();
        for (CrudMode ele : values) {
            if (ele.label().equals(label)) {
                return ele;
            }
        }
        return null;
    }
}
  1. Illegal modifier for the enum constructor; only private is permitted.