아이템 38. 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라. - ksw6169/effective-java GitHub Wiki

열거 타입은 확장할 수 없다.

열거 타입은 거의 모든 상황에서 타입 안전 열거 패턴보다 우수하다. 단, 예외가 하나 있으니 타입 안전 열거 패턴은 확장할 수 있으나 열거 타입은 그럴 수 없다는 점이다.

타입 안전 열거 패턴(typesafe enum pattern)

enum이 도입되기 전(JDK 1.5 이전)에 사용하던 방식으로 제한된 수의 정적 타입을 만들어놓고 사용하는 패턴을 말한다. 정수 열거 패턴의 단점을 해결하고자 제안되었다. 참고로 자바 이펙티브 초판에 기술된 내용이다.

// 정수 열거 패턴(int enum pattern)
public class Text {
    public static final int STYLE_BOLD          = 1 << 0;   // 1
    public static final int STYLE_ITALIC        = 1 << 1;   // 2
    public static final int STYLE_UNDERLINE     = 1 << 2;   // 4
    public static final int STYLE_STRIKETHROUGH = 1 << 3;   // 8

    // 매개변수 styles는 0개 이상의 STYLE_ 상수를 비트별 OR한 값이다.
    public void applyStyles(int styles) { ... }
}
// 타입 안전 열거 패턴(typesafe enum pattern)
public class Suit {
    private final String name;

    public static final Suit CLUBS =new Suit("clubs");
    public static final Suit DIAMONDS =new Suit("diamonds");
    public static final Suit HEARTS =new Suit("hearts");
    public static final Suit SPADES =new Suit("spades");    

    private Suit(String name){
        this.name =name;
    }

    public String toString(){
        return name;
    }
}

열거 타입을 확장하는 건 좋지 않다.

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

  • 확장한 타입의 원소는 기반 타입의 원소로 취급되지만 그 반대는 성립하지 않는다.
  • 기반 타입과 확장된 타입들의 원소 모두를 순회할 방법이 마땅치 않다.
  • 확장성을 높이려면 고려할 요소가 늘어나 설계와 구현이 더 복잡해진다.

열거 타입을 확장하는 것이 좋은 상황도 있다.

하지만 열거 타입을 확장하는 것이 좋은 상황이 하나는 있다. 바로 연산 코드(operation code 혹은 opcode)다. 이따금 API가 제공하는 기본 연산 외에 사용자 확장 연산을 추가할 수 있도록 열어줘야 할 때가 있다.

열거 타입을 확장하는 방법

열거 타입이 인터페이스를 구현할 수 있으므로 인터페이스를 사전 정의하고 이를 구현하여 확장할 수 있다.

public interface Operation {
    double apply(double x, double y);
}
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;
    }
}

열거 타입인 BasicOperation은 확장할 수 없지만 인터페이스인 Operation은 확장할 수 있고, 이 인터페이스를 연산의 타입으로 사용하면 된다. 이렇게 하면 Operation을 구현한 또 다른 열거 타입을 정의해 기본 타입인 BasicOperation을 대체할 수 있다.

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 super.toString();
    }
}

확장된 열거 타입을 사용하는 코드

확장된 열거 타입(ExtendedOperation)은 기존 열거 타입(BasicOperation)을 사용하는 코드에는 전부 사용 가능하다. 이를 사용하는 코드는 다음과 같다.

public static void main(String[] args) {
    double x = Double.parseDouble(args[0]);
    double y = Double.parseDouble(args[1]);
    test(ExtendedOperation.class, x, y);
}

/**
 * class 리터럴은 한정적 타입 토큰으로 사용된다.
 * 여기서 매개변수의 선언은 Class 객체가 열거 타입인 동시에
 * Operation의 하위 타입이어야 한다는 뜻이다.
 */
private static <T extends Enum<T> & Operation> void test(Class<T> opEnumType, double x, double y) {
    for (Operation op : opEnumType.getEnumConstants())
        System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
}

확장된 열거 타입을 사용하는 두 번째 방법은 Class 객체 대신 한정적 와일드카드 타입을 사용하는 방법이다.

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 (Operation op : opSet)
        System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
}

인터페이스를 이용해 열거 타입을 확장하는 방식의 단점

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

참고 자료

  • Effectiva Java 3/E