티스토리 뷰

  • 열거 타입은 거의 모든 상황에서 타입 안전 열거 패턴보다 우수하다.

타입 안전 열거 타입

// 이전에 쓰던 방식 
public final class Season {
  public static final Season SPRING = new Direction("SPRING");
  public static final Season SUMMER = new Direction("SUMMER");
  public static final Season FALL = new Direction("FALL");
  public static final Season WINTER = new Direction("WINTER");

    // ...
}

열거 타입의 단점

  • 타입 안전 열거 패턴은 확장이 가능하지만 열거 타입은 확장할 수 없다.
  • enum 은 java.lang.Enum 을 확장하고 있음.

대부분의 상황에서 열거 타입을 확장하는건 좋지 않은 생각이다.

  • 확장한 타입의 원소는 기반 타입의 원소로 취급하지만 그 반대는 성립하지 않는다면 이상하다.
  • 상위, 하위타입의 모든 원소를 순회하는 방법이 없다.
  • 확장성을 높이려면 고려해야 할 요소가 늘어나 설계와 구현이 더 복잡해진다.

가끔은 enum 을 확장해야 할 수도 있다.

  • 연산코드, 기계마다 가지는 기계가 할 수 있는 연산.
  • 인터페이스 구현을 통해 해결한다.
  • 연산 코드용 인터페이스를 정의하고 열거타입이 구현하도록 한다. 이 열거타입이 표준 구현체 역할을 한다.
// Operation
public interface Operation {
  double apply(double x, double y);
}

// BasicOperation
public 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 symbol;

  BasicOperation(String symbol) {
    this.symbol = symbol;
  }

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

// ExtendedOperation
public enum ExtendedOperation 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 symbol;

    ExtendedOperation(String symbol) {
     this.symbol = symbol;
 }

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

타입 수준으로 다형성을 적용할 수 있다.

// 1. 한정적 타입 매개변수
public static void main(String[] args) {
    double x = Double.parseDouble(args[0]);
    double y = Double.parseDouble(args[1]);
    test(ExtendedOperation.class, x, y);
}

private static <T extends Enum<T> & Operation> void test(
    Class<T> opEnumType, double x, double y) {
    for (Opertaion op : opEnumType.getEnumConstants())
        System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
}
  • (<T extends Enum & Operation> Class): Class 객체가 열거 타입인 동시에 Operation의 하위 타입이어야 한다는 뜻이다.
// 2. 한정적 와일드 카드 타입 
public static void main(String[] args) {
    double x = Double.parseDouble(args[0]);
    double y = Double.parseDouble(args[1]);
    test(Arrays.asList(ExtendedOperation.values()), x, y);
}

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

열거 타입에서 인터페이스를 이용해 확장 하는 경우 사소한 문제점

  • 열거 타입끼리 구현을 상속할 수 없다.
  • 아무 상태에도 의존하지 않는 경우에는 디폴트 구현을 이용해 인터페이스에 추가하는 방법이 있다.

자바 라이브러리 예시

package java.nio.file;

/**
 * Defines the options as to how symbolic links are handled.
 *
 * @since 1.7
 */

public enum LinkOption implements OpenOption, CopyOption {
  /**
   * Do not follow symbolic links.
   *
   * @see Files#getFileAttributeView(Path,Class,LinkOption[])
   * @see Files#copy
   * @see SecureDirectoryStream#newByteChannel
   */
  NOFOLLOW_LINKS;
}
  • OpenOption, CopyOption 을 구현한다.

결론

  • 열거 타입 자체는 확장할 수 없지만, 인터페이스와 그 인터페이스를 구현하는 기본 열거 타입을 함께 사용해 같은 효과를 낼 수 있다.
  • API가 인터페이스 기반으로 작성되었다면 기본 열거 타입의 인스턴스가 쓰이는 모든 곳을 새로 확장한 열거 타입의 인스턴스로 대체해 사용할 수 있다.