item 37) ordinal 인덱싱 대신 EnumMap을 사용하라

ordinal 인덱싱

  • 아래는 LifeCycle 상수의 ordinal 인덱스 값을 배열의 인덱스로 사용하여, 특정 LifeCycle에 해당되는 Plant들을 배열 내부 Set에 추가한다.

  • 배열이 제네릭과 호환되지 않아 비검사 형변환 수행해야 한다.

  • 배열의 각 인덱스에 해당하는 의미를 모르므로 출력 결과에 직접 레이블을 달아주어야 한다.

  • 정수는 열거 타입과 달리 타입 안전하지 않으므로 정확한 정숫값을 사용한다는 것을 직접 보증해야 한다.

public static void usingOrdinalArray(List<Plant> garden) {
    Set<Plant>[] plantsByLifeCycle = (Set<Plant>[]) new Set[LifeCycle.values().length];
    for (int i = 0 ; i < plantsByLifeCycle.length ; i++) {
        plantsByLifeCycle[i] = new HashSet<>();
    }

    for (Plant plant : garden) {
        plantsByLifeCycle[plant.lifeCycle.ordinal()].add(plant);
    }

    for (int i = 0 ; i < plantsByLifeCycle.length ; i++) {
        System.out.printf("%s : %s%n", LifeCycle.values()[i], plantsByLifeCycle[i]);
    }
}

EnumMap

  • 열거 타입을 키로 사용하도록 설계하여, 실질적인 열거 타입 상수를 값으로 매핑하도록 한다.

  • 내부 구현 방식은 배열으로 되어있지만, 내부로 감춰져 Map의 타입 안전성과 배열의 성능을 가진다.

Map<LifeCycle, Set<Plant>> plantsByLifeCycle = new EnumMap<>(LifeCycle.class);

for (LifeCycle lifeCycle : LifeCycle.values()) {
    plantsByLifeCycle.put(lifeCycle,new HashSet<>());
}

for (Plant plant : garden) {
    plantsByLifeCycle.get(plant.lifeCycle).add(plant);
}
  • 스트림과 EnumMap을 함께 사용하는 예제이다.

  • 생애주기에 속하는 Plant 객체가 있을 경우에만 EnumMap의 키가 생성된다.

public static void streamEnumMap(List<Plant> garden) {
    Map plantsByLifeCycle = garden.stream().collect(Collectors.groupingBy(plant -> plant.lifeCycle,
                    () -> new EnumMap<>(LifeCycle.class),Collectors.toSet()));
    System.out.println(plantsByLifeCycle);
}

중첩 EnumMap

  • 이전 상태에서 '이후 상태에서 전이로의 맵'에 대응시키는 맵

  • 실제 내부에서는 맵들의 맵이 배열들의 배열로 구현되니 낭비되는 공간과 시간도 거의 없이 명확하고 안전하고 유지보수하기 좋다.

public enum Phase {
    SOLID, LIQUID, GAS;

    public enum Transition {
        MELT(SOLID, LIQUID),
        FREEZE(LIQUID, 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>> transitionMap = Stream.of(values())
                .collect(Collectors.groupingBy(t -> t.from,
                        () -> new EnumMap<>(Phase.class),
                        Collectors.toMap(t -> t.to,
                                t -> t,
                                (x,y) -> y,
                                () -> new EnumMap<>(Phase.class))));

        public static Transition from(Phase from, Phase to) {
            return transitionMap.get(from).get(to);
        }
    }

}

Last updated