티스토리 뷰

enum type의 성질들이다.

 

  • 컴파일 시 final로 선언된다.
  • 컴파일 시 Enum class를 상속받는다.
  • 개발자가 생성자를 정의할 수 없다.

위의 특징들을 보면서 무엇이 느껴지는가? 이미 상속을 받아 상속을 받을 수 없고 (다중 상속) final로 선언되어 enum을 상속할 수도 없다. (extends는 직역하면 확장이지만 익숙한 상속으로 쓰겠음 -> 이하 확장)

 

그러나 enum type에서 확장의 효과를 누릴 수 있는 법이 있다. interface를 구현하는 법이다.

// 코드 38-1 인터페이스를 이용해 확장 가능 열거 타입을 흉내 냈다. (232쪽)
public interface Operation {
    double apply(double x, double y);
}


// 코드 38-1 인터페이스를 이용해 확장 가능 열거 타입을 흉내 냈다. - 기본 구현 (233쪽)
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;
    }
}

 

반복해서 말하지만 enum type인 BasicOperation은 확장할 수 없지만 그냥 interface 나부랭이인 Operation은 확장할 수 있다. Operation을 구현한 다른 enum type을 통해 BasicOperation을 손쉽게 대체할 수 있다.

 

 

 

 

// 코드 38-2 확장 가능 열거 타입 (233-235쪽)
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;
    }
}

Operation을 구현한 ExtendedOperation이다. 위의 enum type 또한 Operation interface를 사용하는 클라이언트 코드에서 쉽게 호환될 수 있다.

 

 

 

 

이를 사용하는 테스트코드이다.

// 열거 타입의 Class 객체를 이용해 확장된 열거 타입의 모든 원소를 사용하는 예 (234쪽)
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 (Operation op : opEnumType.getEnumConstants())
        System.out.printf("%f %s %f = %f%n",
                x, op, y, op.apply(x, y));
}

<T extends Enum<T> & Operation>이 복잡하다. 의미를 풀이하면 Enum과 Operation을 구현한 클래스여야 한다는 뜻이다. Operation뿐이 아닌 Enum으로 타입을 한정하면 Class의 getEnumConstants() method를 통해 상수 배열을 가져올 수 있다.

Class.getEnumConstants()

만약 Class<T>의 T가 enum type이면 reflection을 통해 values method를 호출하고 생성된 상수 배열을 반환한다.

constants를 field로 지니면서 캐싱을 통해 최적화를 한 모습이 흥미롭다.

 

 

 

Class 객체 대신 한정적 와일드카드 타입으로 유연성을 더 챙길 수 있다.

// 컬렉션 인스턴스를 이용해 확장된 열거 타입의 모든 원소를 사용하는 예 (235쪽)
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));
}

Enum type뿐 아니라 Operation의 apply를 구현한 모든 타입을 순회할 수 있다. 대신 EnumSet이나 EnumMap을 사용할 수는 없다.

 

 

 

 

 

이렇게 interface를 통해 enum을 확장하는 방법에도 문제가 있다. 코드를 공유할 수 없다는 것이다.

 

이것은 interface의 특징 때문이다. 객체의 field를 참조하지 않는다면 java 8부터 생긴 interface의 default method를 사용하면 되지만 field를 참조한다면 그럴 수 없다. interface는 field 자체가 없다.

따라서 같은 기능을 하는 소스 코드를 중복해서 구현해야 하는 문제가 발생한다. 이런 경우에는 해당 코드를 helper class static helper method로 분리해서 코드 중복을 줄일 수 있다. (객체의 필드를 파라미터로 받아서 무언가를 계산해 주는 method를 구현하자.)

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함