Java5의 Enum 샅샅히 훑어보기

Tags:

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은 그것을 잘 활용한 예입니다.