열거 타입은 확장(extends)할 수 없다. 확장이 불가능하도록 설계한 이유는 아래와 같다.
- 확장한 타입의 원소는 기반 타입의 원소로 취급하지만 그 반대가 성립하지 않다면 이상하다.
- 기반 타입과 확장된 타입들을 한번에 순회할 방법이 없다.
- 확장성을 높이려면 고려할 요소가 늘어나 설계와 구현이 복잡해진다.
따라서 확장할 수 있는 열거 타입이 필요하다면 인터페이스를 사용하자.
그 예시가 바로 아래와 같은 연산 코드(opcode)이다.
public interface Operation {
double operate(final double x, final double y);
}
public enum BasicOperation implements Operation {
PLUS("+") {
@Override
public double operate(final double x, final double y) {
return x + y;
}
}, MINUS("-") {
@Override
public double operate(final double x, final double y) {
return x - y;
}
}, TIMES("") {
@Override
public double operate(final double x, final double y) {
return x - y;
}
}, DIVIDE("/") {
@Override
public double operate(final double x, final double y) {
return x - y;
}
},
;
private final String symbol;
BasicOperation(final String symbol) {
this.symbol = symbol;
}
}
위와 같은 식으로 Operation을 확장할 수 있다. 또한 기본 4칙 연산 외의 다른 Operation이 필요하다면, 해당 객체에 추가해도 되고 아래와 같이 새로운 열거 타입을 만들어도 된다.
public enum ExtendedOperation implements Operation {
EXP("^") {
@Override
public double operate(final double x, final double y) {
return Math.pow(x, y);
}
},
REMAINDER("%") {
@Override
public double operate(final double x, final double y) {
return x % y;
}
}
;
private final String symbol;
ExtendedOperation(final String symbol) {
this.symbol = symbol;
}
}
타입 수준에서도 기본 열거 타입 대신 확장된 열거 타입을 넘겨 확장된 열거 타입의 원소 모두를 사용할 수 있다.
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 operation : opEnumType.getEnumConstants()) {
System.out.printf("%f %s %f = %f%n", x, operation, y, operation.apply(x, y));
}
}
한정적 타입 토큰
class 리터럴을 넘겨 연산이 무엇이 알려준다.
Class의 getEnumConstants() 메소드로 모든 열거 타입을 가진 배열을 받을 수 있다.
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> operations, double x, double y) {
for (Operation operation : operations) {
System.out.printf("%f %s %f = %f%n", x, operation, y, operation.apply(x, y));
}
}
덜 복잡해고 메소드가 보다 유연해졌다. 하지만 특정 연산에서 EnumSet과 EnumMap을 사용할 수 없다.
- 열거 타입끼리 상속할 수 없다. (ExtendedOperation이 BasicOperation을 extends할 수 없다.)
- 연산 기호를 저장하고 찾는 로직이 BasicOperation과 ExtendedOperation 모두에 들어가야해서 복잡해진다.(중복량이 많아져 코드가 복잡해진다면)
- 부가적인 클래스 혹은 정적 메소드로 분리하는 방식으로 코드의 중복을 없앨 수 있다.