본문 바로가기

Dev Book Review/Effective Java

[Effective Java] item 37. ordinal 인덱싱 대신 EnumMap을 사용하라

1. 올바르지 않은 방법 : ordinal()을 배열 인덱스로 사용

Set<Plant>[] plant ByLisfeCycle = (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.lifeCycler.ordinal()].add(p);
}
for(int i =0; i<plantsByLifeCycle.length;i++)}{
  System.out.println("%s: %s%n", Plant.LifeCycle.values()[i], plantsByLifeCycle[i]);
}

문제점

  • 배열은 제네릭과 호환되지 않아 비검사 형변환을 수행해야한다. (컴파일이 깔끔하지 않다)
  • 정수 값을 정확히 사용해야한다 : 잘못하면 ArrayIndexOutOfBoundException

 

2. EnumMap : 열거 타입을 키로 사용

Set<Plant>[] plantsByLifeCycleArr =
  (Set<Plant>[]) new Set[Plant.LifeCycle.values().length];
for (int i = 0; i < plantsByLifeCycleArr.length; i++)
  plantsByLifeCycleArr[i] = new HashSet<>();
for (Plant p : garden)
  plantsByLifeCycleArr[p.lifeCycle.ordinal()].add(p);
// 결과 출력
for (int i = 0; i < plantsByLifeCycleArr.length; i++) {
  System.out.printf("%s: %s%n",
                    Plant.LifeCycle.values()[i], plantsByLifeCycleArr[i]);
}

열거 타입을 키로 사용하도록 설계한 Map 구현체이다.

  • 안전하지 않은 형변환 사용하지 않는다.
  • 배열 인덱스 계산 과정 오류 가능성도 없다.
  • 내부에서 배열을 사용해서 ordinal 배열만큼 성능이 나온다 : 낭비 되는 공간과 시간이 거의 없어 명확하고 안전하고 유지보수하기 좋다.

EnumMap의 생성자가 받는 키타입의 Class 객체는 한정적 타입 토큰으로 런타입 제네릭 타입정보를 제공한다.

item37-1

 

3. EnumMap을 stream과 함께 사용할 때

스트림을 사용할 때 고유한 맵 구현체를 사용하기 때문에 EnumMap을 사용할 때 얻는 공간과 성능 이점이 사라진다는 문제가 있다.

System.out.println(Arrays.stream(garden)
                   .collect(groupingBy(p -> p.lifeCycle))); // 고유한 맵 구현체
System.out.println(Arrays.stream(garden)
                  .collect(groupingBy(p -> p.lifeCycle, // 원하는 맵 구현체 명시
                                     () -> new EnumMap<>(LifeCycle.class), toSet())));

stream을 사용하면 enumMap 만 사용했을 때와 생성되는 맵의 개수가 다르다.

// 코드 37-2 EnumMap을 사용해 데이터와 열거 타입을 매핑한다.
System.out.println("using EnumMap");
Map<Plant.LifeCycle, Set<Plant>> plantsByLifeCycle =
  new EnumMap<>(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);

System.out.println("using stream");
// 코드 37-3 스트림을 사용한 코드 1 - EnumMap을 사용하지 않는다!
Map<LifeCycle, List<Plant>> collect = Arrays.stream(garden)
  .collect(groupingBy(p -> p.lifeCycle));
System.out.println(collect);

System.out.println("using stream enumMap");
// 코드 37-4 스트림을 사용한 코드 2 - EnumMap을 이용해 데이터와 열거 타입을 매핑했다.
EnumMap<LifeCycle, Set<Plant>> collect1 = Arrays.stream(garden)
  .collect(groupingBy(p -> p.lifeCycle,
                      () -> new EnumMap<>(LifeCycle.class), toSet()));
System.out.println(collect1);

 

item37-2

EnumMap을 이용해 put한 코드드에서는 PERENNIAL이 없어도 생성된다. 반면 stream 을 이용한 코드에서는 2개만 생성된다.