enum 자료형은 형 안전 enum 패턴보다 거의 모든 면에서 월등하다. 그러나 형 안전 enum 패턴은 계승을 통한 확장이 가능했단 반면 enum 자료형은 그 렇지 않다. 다시 말해서 형 안전 enum 패턴을 쓸 경우에는 다른 열거 자료형을 계승해서 새로운 열거 자료형을 만드는 것이 가능하지만 enum 자료형으로는 그럴 수 없다는 이야기다. 그러나 이것을 단점이라 볼 수 는 없는데 enum 자료형을 계승한다는 것을 대체로 바람직하지 않기 때문. 확장된 자료형의 상수들이 기본 자료형의 상수가 될 수 있다는 것, 그러나 그 반대는 될 수 없다는 것이 혼란 스럽다. 게다가 기본 자료형과 그 모든 하위 자료형의 enum 상수들을 순차적으로 살펴볼 좋은 방법도 없다. 마지막으로 계승을 허용하게 되면 설계와 구현에 관련된 많은 부분이 까다로워진다. (흠..)

하지만 열거 자료형의 확장이 가능하면 좋은 경우가 적어도 하나는 있다. 연산 코드를 만들어야 할 때다. 아래 코드를 보자


enum BasicOperation implements Operation { PLUS("+"){ public double apply(double x, double y) { return x + y; } }, MINUS("-"){ public double apply(double x, double y) { return x - y; } }, TIMES("*"){ public double apply(double x, double y) { return x * y; } }, DIVIDE("/"){ public double apply(double x, double y) { return x / y; } }; private final String s; BasicOperation(String s) { this.s = s; } @Override public String toString(){ return s; } } interface Operation { double apply(double x, double y); }

BasicOperation은 enum 자료형이라 계승할 수 없지만 Operation은 인터페이스라 확장이 가능하다. API가 사용하는 모든 연산은 이 인터페이스로 표현한다. 따라서 이 인터페이스를 계승하는 새로운 enum 자료형을 만들면 Operation 객체가 필요한 곳에 해당 enum 자료형의 상수를 이용할 수 있게 된다. 위의 기본 연산을 확장해서 지수 연산과 나머지 연산을 추가하고 싶다고 해보자.

enum ExtendedOperration implements Operation {
  EXP("^") {
    public double apply(double x, double y) {
      return Math.pow(x, y);
    }
  },
  REMAINDER("%") {
    public double apply(double x, double y) {
      return x % y;
    }
  };
  private final String s;

  ExtendedOperration(String s) {
    this.s = s;
  }

  @Override
  public String toString() {
    return s;
  }
}

새로 만든 연산들은 기존 연산들이 쓰였던 곳에는 어디든 사용할 수 있다. API가 BasicOperation이 아니라 Operation을 사용하도록 작성되어 있기만 하면 된다. 상수별 메서드 구현을 이용해서 확장 불가능 enum을 만들 때는 apply 메서드를 abstract로 선언할 필요가 있었는데 여기서는 그럴 필요가 없다.

아래 코드를 보자

public static void main(String[] args) {
  test(ExtendedOperration.class, 20, 14);
}

private static <T extends Enum<T> & Operation> void test(Class<T> opSet, double x, double y) {
  for (Operation op : opSet.getEnumConstants()) {
    System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
  }
}

확장된 연산을 나타내는 자료형의 class 리터럴인 ExtendedOperration.class가 main에서 test로 전달되고 있음에 유의하자. 확장된 연산 집합이 무엇인지 알리기 위한 것이다. 이 class 리터럴은 한정적 자료형 토큰 구실을 한다. opSet의 형인자 T는 굉장히 복잡하게 선언 되었는데
& Operation>, class 객체가 나타내는 자료형이 enum 자료형인 동시에 Operation의 하위 자료형이 되도록 한다.

다음으로는 한정적 와일드카드 자료형을 opSet인자의 자료형으로 사용하는 것이다.

public static void main(String[] args) {
  test1(Arrays.asList(ExtendedOperration.values()), 20, 14);
}

private static void test1(Collection<? extends Operation> opSet, double x, double y) {
  for (Operation op : opSet) {
    System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
  }
}

조금 덜 복잡한 코드가 만들어지긴 하지만, test 메서드의 코드는 조금 더 복잡해졌다. 메서드를 호출할 때 여러 enum 자료형에 정의한 연산들을 함께 전달할 수 있도록 하기 위한 것이다. 그러나 이렇게 하면 EnumSet이나 EnumMap을 사용할 수 없기 때문에 여러 자료형에 정의한 연산들을 함께 전달할 수 있도록 하는 유연성이 필요 없다면, 한정적 자료형 토큰을 쓰는 편이 낫다.

정리

계승 가능 enum 자료형은 만들 수 없지만, 인터페이스를 만들고 그 인터페이스를 구현하는 기본 enum 자료형을 만들면 계승 가능 enum 자료형을 흉내 낼 수 있다. 그렇게 하면 클라이언트가 해당 인터페이스를 구현하는 enum 자료형을 손수 만들 수 있다. API가 인터페이스를 이용해 작성 되었다면, 그런 enum자료형은 기본 enum 자료형이 쓰이는 곳에는 어디든 사용할 수 있다.