핵심 정리
열거 타입은 확장할 수 없다. 그러나 인터페이스와 그 인터페이스를 구현하는 열거 타입을 사용하면 비슷한 효과를 낼 수 있다. But 열거 타입끼리의 상속을 구현할 수는 없다.
열거 타입 vs 타입 안전 열거 패턴
// 열거 타입
public enum Day {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
}
// 타입 안전 열거 패턴
public class Day {
public static final Day MONDAY = new Day("MONDAY");
public static final Day TUESDAY = new Day("TUESDAY");
// 생략
private final String name;
private Day(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
타입 안전 열거 패턴은 enum이 도입되기 전에 사용되던 패턴으로, enum이 거의 모든 상황에서 우수하다.
그러나 enum은 확장할 수 없고, 타입 안전 열거 패턴은 확장할 수 있다.
public enum Color {
RED,
GREEN,
BLUE
}
public enum ExtendedColor extends Color { // 불가능
YELLOW,
ORANGE,
PURPLE
}
위 코드는 애초에 허용되지 않는다. 이유는 아래와 같다.
1. 확장한 타입의 원소를 기반 타입의 원소로 취급하지만 그 반대는 성립하지 않는다.
ExtendedColor의 원소는 Color로 취급할 수 있으나, 그 반대는 성립하지 않음 ! (다형성 위반)
2. 기반 타입과 확장된 원소 모두를 순회할 방법이 마땅치 않다.
Color의 모든 원소를 순회하려면 Color.values()를 사용하여 RED, GREEN, BLUE만 얻을 수 있다. 즉, YELLOW, ORANGE, PURPLE은 얻을 수 없다.
3. 확장성을 높이면 설계와 구현이 더 복잡해진다.
메소드 오버라이딩, 다형성, 업 캐스팅, 다운캐스팅 등을 모두 고려해야 하므로, 코드의 복잡성을 증가시킨다.
인터페이스를 이용해 열거 타입을 확장할 수 있다.
: 열거 타입은 인터페이스를 구현할 수 있으므로 ..
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;
}
}
위 코드에서 연산 타입을 확장해보자.
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;
}
}
첫 번째 방법 : class 리터럴 넘기기
public static void main(String[] args) {
double x = Double.parseDouble(args[0]);
double y = Double.parseDouble(args[1]);
test(ExtendedOperation.class, x, y);
}
// 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 객체 대신 한정적 와일드카드 타입인 Collection<? extends Operation> 넘기기
해당 방법은 BasicOperation과 ExtendedOperaion 인스턴스 모두를 처리한다. 그러나 EnumSet 또는 EnumMap은 특정 enum 타입에 대해서만 작동하므로 사용할 수 없다.
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));
}
인터페이스를 이용해 확장 가능한 열거 타입을 흉내내는 방식의 문제점
: 열거 타입끼리 구현을 상속할 수 없음
'Language > Java' 카테고리의 다른 글
| [effective java] 아이템 40. @Override 애너테이션을 일관되게 사용하라. (0) | 2023.07.02 |
|---|---|
| [effective java] 아이템 39. 명명 패턴보다 애너테이션을 사용하라. (0) | 2023.07.02 |
| [effective java] 아이템 37. ordinal 인덱싱 대신 EnumMap을 사용하라. (0) | 2023.07.02 |
| [effective java] 아이템 36. 비트 필드 대신 EnumSet을 사용하라. (0) | 2023.05.14 |
| [effective java] 아이템 35. ordinal 메서드 대신 인스턴스 필드를 사용하라. (0) | 2023.05.14 |