// 식물을 아주 단순하게 표현한 클래스 (226쪽)
class Plant {
enum LifeCycle { ANNUAL, PERENNIAL, BIENNIAL }
final String name;
final LifeCycle lifeCycle;
Plant(String name, LifeCycle lifeCycle) {
this.name = name;
this.lifeCycle = lifeCycle;
}
@Override public String toString() {
return name;
}
정원에 심은 식물들을 배열 하나로 관리하고, 이들을 생애주기(한해살이, 여러해살이, 두해살이)별로 묶어보자. 생애주기별로 총 3개의 집합을 만들고 정원을 한 바퀴 돌며 각 실물을 해당 집합에 넣는다.어떤 프로그래머는 집합들을 배열 하나에 넣고 생애주기의 ordinal 값을 그 배열의 인덱스로 사용하려 할 것이다.
안좋은 코드
Set<Plant>[] plantsByLifeCycle = (Set<Plant>[]) new Set[Plant.LifeCycle.values().length];
for (int i = 0; i < plantsByLifeCycle.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]);
}
배열은 제네릭과 호환되지 않으니(아이템 28) 비검사 형변환을 수행해야 하고 깔끔히 컴파일되지 않는다.(?)
또 정확한 정숫값을 사용한다는 것을 직접 보장해야 한다는 점이다. 잘못된 값을 사용하면 잘못된 동작을 수행하거나 ArrayIndexOutOfBoudnsException을 던질 것이다.
여기서 배열은 실질적으로 열거 타입 상수를 값으로 매핑하는 일을 한다. 그러면 Map을 사용할 수 있는데, **열거 타입을 키로 사용하도록 설계된 아주 빠른 Map 구현체가 EnumMap**이다.
``` java
Map<Plat.LifeCyfcle, Set<Plat>> platsByLifeCycle = new EnumMap<>(Play.LifeCycle.class);
for (Plant.LifeCycle lc : Plant.LifeCycle.values())
playsByLifeCycle.put(lc, new HashSet<>());
for (Plant p : garden)
plantsByLifeCycle.get(p.lifeCycle).add(p);
System.out.println(plantsByLifeCycle);
안전하지 않은 형변환은 쓰지 않고, 맴의 키인 열거 타입이 그 자체로 출력용 문자열을 제공하니 출력 결과에 직접 레이블을 달 일도 없다. 또 배열 인덱스를 계산하는 과정에서 오류가 날 가능성도 없다.
EnumMap의 성능이 ordinal을 쓴 배열과 비슷한 이유는 내부에서 배열을 사용하기 때문이다.
스트림을 사용한 코드
System.out.println(Arrays.stream(garden)
.collect(groupingBy(p -> p.lifeCycle, () -> new EnumMap<>(LifeCycle.class), toSet())));
스트림을 사용하면 EnumMap만 사용했을 때와 살짝 다르다. EnumMap 버전은 언제나 식물의 생애주기당 하나씩의 중첩 맵을 만들지만, 스트림 버전에서는 해당 생애주기에 속하는 식물이 있을 때만 만든다.