Java5에서 Enum은 기존의
public interface XYZConstants { public static final int A = 1; public static final int B = 2; public static final int C = 3; }
가 갖는 문제점을 해결합니다. 위와같은 integer를 사용한 constants는 결국
int c = XYZConstatnts.B;
와 같은 형태로 값을 저장해야하고 따라서 아무 값이나 c안에 저장할 수 있다는 문제가 있습니다. 이를 해결학위한 Typesafe Enumeration 패턴이 있지만 이는 구현하기 어렵고, switch-case문을 사용하지 못하는 한계가 있죠. 이를 해결한 것이 Java5의 Enum입니다.
Enum은 보통 다음과 같이 선언합니다.
public enum SimpleEnum { A, B, C; public static void main(String[] args) { System.out.println(A); SimpleEnum s = B; switch(B) { case A: System.out.println("A"); break; case B: System.out.println("B"); break; case C: System.out.println("C"); } } }
출력은 다음과 같습니다.
A B
인자를 저장시켜 활용할 수도 있습니다.
public enum EnumWithArgument { A(0,1), B(1, 2), C(2, 3); private int first; private int second; // No modifier is allowed here. EnumWithArgument(int a, int b) { first = a; second = b; } public String toString() { StringBuilder sb = new StringBuilder(); sb.append(first); sb.append(", "); sb.append(second); return sb.toString(); } public static void main(String[] args) { System.out.println(A); } }
출력은 다음과 같습니다.
0, 1
흥미로운 것은 Enum의 hierarchy입니다. 만약 단순히 Enum을 선언하면 hierarchy는 다음과 같이 됩니다.
public enum EnumSuper { A; public static void main(String[] args) { System.out.println(A.getClass()); System.out.println(A.getClass().getSuperclass()); } }
출력은 다음과 같습니다.
class EnumSuper class java.lang.Enum
하지만 다음과 같이 Enum을 추상클래스로 만들고 각각의 item이 override하게 할 수도 있습니다.
public enum EnumSuper2 { A() { @Override public void foo() { } }; abstract void foo(); public static void main(String[] args) { System.out.println(A.getClass()); System.out.println(A.getClass().getSuperclass()); } }
출력은 다음과 같습니다.
class EnumSuper2$1 class EnumSuper2
따라서 각 Enum 안의 value에 polymorphism 적용이 가능하게 됩니다.
public enum EnumSuper3 { A { @Override public void foo() { System.out.println("I am A"); } }, B { @Override public void foo() { System.out.println("I am B"); } }; abstract public void foo(); public static void main(String[] args) { EnumSuper3 e = A; e.foo(); e = B; e.foo(); } }
출력은 다음과 같습니다.
I am A I am B
switch-case를 써서 다음과 같이 한 곳에서 처리하게 할 수도 있습니다.
public enum EnumSuper3 { A, B; public void foo() { switch(this) { case A: System.out.println("I am A"); break; case B: System.out.println("I am B"); break; } } public static void main(String[] args) { EnumSuper3 e = A; e.foo(); e = B; e.foo(); } }
출력은 마찬가지입니다.
I am A I am B
이렇게까지 보고 “기능은 참 많구나…” 하고 끝내면 좋은데 문제는 Enum의 정의된 모양이 무척 이해하기 어렵다는 것입니다. JDK API에 Enum은 다음과 같이 정의되어 있습니다.
Class Enum<E extends Enum<E>> implements Serializable, Comparable<E> { ... }
먼저 Enum은 상수이므로 Serializable 해야한다는 것은 명확합니다. 또 Enum의 각 value간 비교가 가능해야할 것이므로 Comparable<E> 을 구현한 것도 이해할 수 있습니다. 문제는 Enum<E extends Enum<E>>입니다.
앞서 이야기 드렸던 바와 같이 enum으로 선언한 클래스는 Enum을 상속받습니다. 그리고 이 동작은 내부적으로 자동으로 이루어집니다. 따라서 public enum Foo는 Enum<Foo extends Enum> 을 만족합니다. 문제는 Foo extends Enum<Foo> 부분에서 부모 클래스에 넘기는 Foo 입니다. 이것은 generics에서 상속을 강제할 경우 어떤일이 생기는가를 따져보면 이해할 수 있습니다.
1) 여기에서 보다시피 자식 클래스는 부모의 클래스를 물려받으며, 의무적으로 부모의 abstract 메소드를 구현해야 합니다.
2) 또한 covarint 를 가능하게 합니다. 예를들어 Enum 클래스안에 public <E> E method() 가 있었다면 이 E 는 자동으로 enum 으로 선언된 클래스 (지금의 예에서는 Foo)로 치환됩니다.
이처럼 java generics 의 특징은 C++에서 코드가 직접 생성되는 경우와 매우 다른 특징을 가지며 Enum은 그것을 잘 활용한 예입니다.