배열이나 리스트에서 원소를 꺼낼 때, ordinal
메서드로 인덱스를 얻을 수 있지만, 사용하지 말자!
class Plant {
enum LifeCycle { ANNUAL, PERPENNIAL, BIENNIAL }
final String name;
final LifeCycle;
Plant(String name, LifeCycle lifeCycle) {
this.name = name;
this.lifeCycle = lifeCycle;
}
@Override public String toString() {
return name;
}
}
-
ordinal()
을 배열 인덱스로 사용한 경우Set<Plant>[] plantsByLifeCycle = (Set<Plant>[]) new Set[Plant.LifeCycle.values().length]; for(int i = 0; i < plantsByLife.length; i++) plantsByLifeCycle[i] = new HashSet<>(); for(Plant p: garden) plantsByLifeCycle[p.lifeCycle.ordinal()].add(p); //결과 출력 for(int i = 0; i < plantsByLifeCycle.length; i++) { System.out.printf("%s: %s%n", Plant.LifeCycle.values()[i], plantsByLifeCycle[i]); }
- 문제점
- 배열은 제네릭과 호환되지 않기 때문에 비검사 형변환을 수행해야 하고, 깔끔하게 컴파일되지 않는다.
- 배열은 각 인덱스의 의미를 모르기 때문에 출력 결과에 직접 레이블을 달아야 한다.
- 정확한 정숫값을 사용하는 것을 직접 보증해야 한다.
EnumMap
을 사용하여 열거 타입 상수를 값으로 매핑할 수 있다.
- 문제점
-
EnumMap
을 사용하여 데이터와 열거 타입을 매핑하는 경우Map<Plant.LifeCycle, Set<Plant>> plantsByLifeCycle = new EnumMp<>(Plant.LifeCycle.class); for(Plant.LifeCycle lc: Plant.LifeCycle.values()) plantsByLifeCycle.put(lc, new HashSet<>()); for(Plant p: garden) plantsByLifeCycle.get(p.lifeCycle).add(p); System.out.println(plantsByLifeCycle);
- 맵의 키인 열거 타입이 그 자체로 출력용 문제열을 제공하기 때문에 출력 결과에 직접 레이블을 달지 않아도 된다.
- 배열 인덱스를 계산하지 않기 때문에 오류 가능성이 줄어든다.
-
스트림을 사용한 코드 -
Enum
을 사용하지 않는다.System.out.println(Arrays.stream(garden)) .collect(groupingBy(p -> p.lifeCycle, () -> new EnumMap<>(LifeCycle.class), toSet()));
Enum
이 아닌 고유한 맵 구현체를 사용했기 때문에Enum
을 사용해서 얻은 공간과 성능 이점이 사라진다는 문제가 있다.
-
스트림을 사용한 코드 -
EnumMap
을 사용하여 데이터와 열거 타입을 매핑했다.System.out.println(Arrays.stream(garden) .colect(groupingBy(p -> p.lifeCycle, () -> new EnumMap<>(LifeCycle.class), toSet())));
EnumMap
버전은 식물의 생애주기당 하나씩 중첩 맵을 만들지만, 스트림 버전에서는 해당 생애주기에 속하는 식물이 있을 때만 만든다.- e.g) 정원에 한해살이와 여러해살이 식물만 살고, 두해살이는 없다면,
EnumMap
버전에서는 맵을 3개 만들고, 스트림 버전에서는 2개만 만든다.
-
두 열거 타입을 매핑하기 위해
ordinal
을 두 번 호출하는 방식을 사용하지 말자!public enum Phase { SOLID, LIQUID, GAS; public enum Transition { MELT, FREEZE, BOIL, CONDENSE, SUBLIME, DEPOSIT; // 행은 from의 ordinal을, 열은 to의 ordinal을 인덱스로 사용한다. private static final Transision[][] TRANSITIONS = { { null, MELT, SUBLIME }, { FREEZE, null, BOIL }, { DEPOSIT, CONDENSE, null } }; // 한 상태에서 다른 상태로의 전이를 반환한다. public static Transition from(Phase from, Phase to) { return TRANSITIONS[from.ordinal()][to.ordinal()]; } } }
- 해당 코드는 컴파일러가
ordinal
과 배열 인덱스의 관계를 알 수 없다. 따라서Phase
나Phase.Transition
열거 타입을 수정하면서 상전이 표TRANSITION
을 함께 수정하지 않거나 실수로 잘못 수정하면 런타임 오류가 날 수 있다.
- 해당 코드는 컴파일러가
-
중첩
EnumMap
으로 데이터와 열거 타입 쌍을 연결할 수 있다.public enum Phase { SOLID, LIQUID, GAS; public enum Transition { MELT(SOLID, LIQUID), FREEZE(LIAUID, SOLID), BOIL(LIQUID, GAS), CONDENSE(GAS, LIQUID), SUBLIME(SOLID, GAS), DEPOSIT(GAS, SOLID); } private final Phase from; private final Phase to; Transition(Phase from, Phase to) { this.from = from; this.to = to; } // 상전이 맵을 초기화한다. private static final Map<Phase, Map<Phase, Transition>> m = Stream.of(values()).collect(groupingBy(t -> t.from, () -> new EnumMap<>(Phase.class), toMap(t -> t.to, t -> t, (x, y) -> y, () -> new EnumMap<>(Phase.class)))); public static Transition from(Phase from, Phase to) { return m.get(from).get(to); } }
- 중첩
EnumMap
을 사용한다면 코드가 복잡해질 수 있지만, 가독성이 높아지며, 새로운 상태를 간단하게 추가할 수 있다.
- 중첩
- 배열의 인덱스를 얻기 위해
ordinal
을 사용하는 것은 일반적으로 좋지 않으니,EnumMap
을 사용하라! - 다차원 관계는
Enum<..., EnumMap<...>>
으로 표현할 수 있다.