Skip to content

Latest commit

 

History

History
171 lines (125 loc) · 4.68 KB

37. ordinal 인덱싱 대신 EnumMap을 사용하라.md

File metadata and controls

171 lines (125 loc) · 4.68 KB

[아이템 37] ordinal 인덱싱 대신 EnumMap을 사용하라

enum Rule { DUCK, GOOSE }

class Player {

    private final String nickname;
    final Rule rule;

    Player(String nickname, Rule job) {
        this.nickname = nickname;
        this.rule = job;
    }
}
public static void main(String[] args){
    Set<Player>[] playerByJob = (Set<Player>[]) new Set[Rule.values().length];
    List<Player> players = Collections.EMPTY_LIST;

    for (int i = 0; i < playerByJob.length; i++) {
        playerByJob[i] = new HashSet<>();
    }

    for (Player player : players) {
        playerByJob[player.rule.ordinal()].add(player);
    }
}
  • ordinal() 을 이용하여 추가하게 되면 문제가 있다.

  • 제네릭 배열 : 비검사 형변환을 수행해야 하며, 컴파일 할때 깨끗하게 되지 않는다.

  • 배열은 각 인덱스의 의미를 모르므로 직접 레이블을 달아주어야 한다.

  • 정확한 정숫값을 사용한다는 것을 직접 보증해주어야 한다.

    → 잘못된 값을 사용하게 되면 동작이 이상하거나 ArrayIndexOutOfBoundsException을 던진다.

EnumMap


  • 열거 타입을 키로 사용하도록 설계한 아주 빠른 Map 구현체
public static void main(String[] args){
    Map<Rule, Set<Player>> playerByJob = new EnumMap<>(Rule.class);
    List<Player> players = Collections.EMPTY_LIST;
    for (Rule rule : Rule.values()) {
        playerByJob.put(rule, new HashSet<>()); // G
    }
    for (Player player : players) {
        playerByJob.get(player.rule).add(player);
    }
}
  • 안전하지 않은 형변환이 없다.
  • 배열 인덱스를 다루지 않아도 된다.
  • EnumMap 내부에서 배열을 사용하기 때문에 EnumMap을 사용하면 타입 안전성배열의 성능을 모두 얻어낼 수 있다.
  • new EnumMap<>(Rule.class);
    • 한정적 타입 토큰 → 런타임 제네릭 타입 정보 제공

스트림 예시


player

enum Rule { DUCK, GOOSE }

class Player {

    private final String nickname;
    final Rule rule;

    Player(String nickname, Rule job) {
        this.nickname = nickname;
        this.rule = job;
    }
}

Map으로 생성

System.out.println(players.stream()
                .collect(groupingBy(p -> p.rule))); // 일반 Map
  • 키를 Rule로 사용하고, Rule에 매핑되는 값들로 List 생성

EnumMap으로 생성

System.out.println(
      players.stream()
      .collect(groupingBy(p -> p.rule, 
              **() -> new EnumMap<>(Rule.class)**, 
              toSet()))
);
  • 스트림 버전에서는 플레이어가 모두 DUCK인 경우는 Goose에 대응하는 Set이 존재하지 않는다.

두 열거 타입 값 매핑


enum Level {
    LEVEL1, LEVEL2;

    public enum LevelChange {
        LEVEL_UP_1_TO_2, LEVEL_DOWN_2_TO_1;
        private static final LevelChange[][] TRANSITIONS = {
                {null, LEVEL_UP_1_TO_2},
                {LEVEL_DOWN_2_TO_1, null}
        }; // LEVEL1, LEVEL2의 매핑 정보 등록

        public static LevelChange from(Level fromLevel, Level toLevel) {
            return TRANSITIONS[fromLevel.ordinal()][toLevel.ordinal()];
        }
    }

}
  • ordinal과 배열 인덱스의 관계를 알 방법이 없음.

    • Level이나 Level.LevelChange를 변경하면서 TRANSITIONS 배열을 잘못 변경하게 되면 런타임 오류가 발생할 수도 있다.
    • EnumMap을 사용해서 정리하는게 훨씬 낫다.
    enum Level {
        LEVEL1, LEVEL2;
    
        public enum LevelChange {
            LEVEL_UP_1_TO_2(LEVEL1, LEVEL2), LEVEL_DOWN_2_TO_1(LEVEL2, LEVEL1);
            
            private final Level from;
            private final Level to;
    
            LevelChange(Level from, Level to) {
                this.from = from;
                this.to = to;
            }
            
            private static final Map<Level, Map<Level, LevelChange>> m =
                    Stream.of(values()).collect(groupingBy(t -> t.from, ()-> new EnumMap<>(Level.class), toMap(
                            t -> t.to, t -> t, (x, y) -> y, () -> new EnumMap<>(Level.class)
                    )));
    
            public static LevelChange from(Level fromLevel, Level toLevel) {
                return m.get(fromLevel).get(toLevel);
            }
        }
    
    }

정리


  • 배열의 인덱스를 얻기 위해 ordinal을 쓰는 것은 일반적으로 좋지 않으므로 EnumMap을 사용하라.
    • 다차원 관계는 EnumMap<..., EnumMap<...>>으로 표현하라.
    • Enum.ordinal웬만해서는 사용하지 말자.