본문 바로가기
Java/Effective Java 3E

[이펙티브자바 3판] ITEM37. Ordinal 인덱싱 대신 EnumMap을 사용하라

by 잭피 2020. 11. 14.

이번장의 핵심은...

배열의 인덱스를 얻기 위해 ordinal을 쓰는 것은 일반적으로 좋지 않으니, 대신 EnumMap을 사용하라.


몇몇 예를 통해 알아보자

 

Ex) 정원에 심은 식물들을 배열 하나로 관리하고, 생애주기(한해살이, 여러해살이, 두해살이)별로 묶어보자

class Plant {
    enum LifeCycle{
      ANNUAL, PERENNIAL, BIENNIAL
    }

    final String name;
    final LifeCycle lifeCycle;

    public Plants(String name, LifeCycle lifeCycle) {
        this.name = name;
        this.lifeCycle = lifeCycle;
    }

    @Override
    public String toString() {
        return name;
    }
}
Plant[] garden = {
            new Plant("바질",    LifeCycle.ANNUAL),
            new Plant("캐러웨이", LifeCycle.BIENNIAL),
            new Plant("딜",      LifeCycle.ANNUAL),
            new Plant("라벤더",   LifeCycle.PERENNIAL),
            new Plant("파슬리",   LifeCycle.BIENNIAL),
            new Plant("로즈마리", LifeCycle.PERENNIAL)
        };

// 코드 37-1 ordinal()을 배열 인덱스로 사용 - 따라 하지 말 것! (226쪽)
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]);
}

Ordianl() 인덱싱 방식은 쓰지말자!!

배열은 제네릭 호환이 되지 않고, 정확한 정수값을 사용한다는 것을 우리가 보증해야하는 위험이 있다

-> EnumMap을 사용하자

 

EnumMap 방식

EnumMap은 열거 타입을 키로 사용하도록 설계한 아주 빠른 Map 구현체이다

// 코드 37-2 EnumMap을 사용해 데이터와 열거 타입을 매핑한다. (227쪽)
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);
// {ANNUAL=[바질, 딜], PERENNIAL=[로즈마리, 라벤더], BIENNIAL=[캐러웨이, 파슬리]}
```

맵의 키인 열거 타입이 그 자체로 출력용 문자열을 제공한다

배열 인덱스를 계산하는 과정에서 오류가 날 가능성도 없다

더 짧고 명료하고 안전하고 성능도 원래 버전과 비등하다

EnumMap의 성능이 ordinal을 쓴 배열에 비견되는 이유는 그 내부에서 배열을 사용하기 때문이다

내부 구현 방식이 안으로 숨겨져 Map의 타입 안정성과 배열의 성능을 모두 얻어낸다

 

스트림을 사용

1. EnumMap을 사용하지 않음

고유한 맵 구현체를 사용했기 때문에 EnumMap을 써서 얻은 공간과 성능의 이점이 사라지는 문제가 있다

System.out.println(Arrays.stream(garden)
                .collect(groupingBy(p -> p.lifeCycle)));

2. EnumMap을 사용

Collectors.groupingBy 메서드는 mapFactory 매개변수에 원하는 맵 구현체를 명시해 호출할 수 있다

System.out.println(Arrays.stream(garden)
                .collect(groupingBy(p -> p.lifeCycle,
                        () -> new EnumMap<>(LifeCycle.class), toSet())));

 

두 열거 타입 값들을 매핑

전이 하나를 얻으려면 이전 상태(from)와 이후 상태(to)가 필요하니, 맵 2개를 중첩하면 쉽게 해결할 수 있다

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;
        }

        
        public static final Map<Phase, Map<Phase, Transition>> m = Stream.of(values())
                .collect(groupingBy(t -> t.from, () -> new EnumMap<>(Phase.class),
                        toMap(t -> t.to, //key-mapper
                                t -> t,  //value-mapper
                                (x, y) -> y, //merge-function
                                () -> new EnumMap<>(Phase.class))));

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

새롭게 추가될때 Enum type만 추가하면 된다

 

 

이 글은 “이펙티브 자바 3판” 책 내용을 정리한 글입니다.

만약 저작권 관련 문제가 있다면 “shk3029@kakao.com”로 메일을 보내주시면, 바로 삭제하도록 하겠습니다.

 

댓글