index로 접근하는 것은 항상 ArrayIndexOutOfBoundsException에 노출된다. 이렇게 검출되는 것도 운이 좋은 케이스이고, 그렇지 않은 경우는 런타임 상에서 로직을 실수하기 쉽다.
예제를 통해 알아보자
public class Plant {
public enum LifeCycle {ANNUAL, PERENNIAL, BIENNIAL}
private final String name;
private final LifeCycle lifeCycle;
Plant(String name, LifeCycle lifeCycle) {
this.name = name;
this.lifeCycle = lifeCycle;
}
@Override
public String toString() {
return name;
}
}
식물은 생애 주기 타입을 가지고 있고 이를 상태로 활용한다. 이제 우리는 이 식물 타입을 활용해서 생애주기 별로 식물을 묶어서 관리하고 싶다.
이런 경우 Map이 적당하긴하지만 누군가는 Ordinal을 활용해 배열로 보관하고 싶은 생각이 들 수도 있다. 그것의 문제점을 파헤쳐보자
Set<Plant>[] plantsByLifeCycle = (Set<Plant>[]) new Set[Plant.LifeCycle.values().length];
for (int i = 0; i < palntsByLifeCycle.length; ++i) {
plantsByLifeCycle[i] = new HashSet<>();
}
for (Plant p : garden) {
plantsByLifeCycle[p.lifeCycle.ordinal()].add(p);
}
사실 이전에 ordinal()는 라이브러리 내부 기능상 사용하고 사용자는 쓸일이 없다는데 위 코드를 보자. 우성 배열이기 때문에 제네릭과 호환되지 않기 때문에 비검사 형변환을 수행해야 하고 컴파일이 깔끔하지 않다. 배열의 각 의미는 알 수 없으니 직접 레이블을 달아야하고, index로 접근하는 것은 타입 안전하지 않을 가능성이 있다. 실수할 가능성이 높아지는 것이다. 이를 막기 위해 java에서 EnumMap이란 것을 제공한다
enumMap은 Map 인터페이스로 구현했기 때문에 Map과 사용법이 똑같다. 그리고 내부는 배열로 이루어져있기 때문에 Enum을 키로 사용한다면 Map보다 훨씬 빠르고 효율이 좋다. 그리고 쉽게 작성할 수 있다.
Arrays.stream(garden)
.collect(groupingBy(p -> p.lifeCycle,
() -> new EnumMap<>(LifeCycle.class), toSet())));