본문 바로가기

Programming/JAVA

[Effective JAVA 3SE] item 37: ordinal 인덱싱 대신 EnumMap을 사용하라

ordinal 메서드(아이템 35)

  • 배열 혹은 리스트에서 원소를 꺼내기 위해 이를 사용해 인덱스를 얻는 경우가 있음
  • ordinal 메서드는 인스턴스가 가진 EnumName의 Index 번호를 넘겨줌

  • 코드 37-1의 문제 겸, ordinal 메서드 사용의 문제
    • 배열과 제네릭은 호환되지 않기에 비검사 형변환 수행을 해야 하며, 이로 깔끔히 컴파일되지 않을 것임
    • 배열은 숫자로 된 데이터로 인덱스의 의미를 모르니 출력 겨로가에 레이블을 달아야 함
    • 정숫값을 사용한다는 것을 직접 보증해야 함
    • 정수는 열거 타입과 달리 안전하지 않음
      • 잘못된 값을 사용할 경우
        • 그대로 잘못된 수행을 하거나 (ㅋㅋㅋ 최악의 상황 ㅠ)
        • 운이 좋을 경우 ArrayIndexOutOfBoundsException을 던짐

위 문제 해결 :: EnumMap

  • 열거 타입 상수를 값으로 매핑해 Map으로 사용할 수 있음
  • 열거 타입을 키로 사용하도록 설계되어 빠른 처리 가능

 

  • 예제 코드 37-2 참고
    • EnumMap을 사용해 짧고, 명료하며, 안전하고 성능도 원래 버전과 비등하게 만들 수 있음
    • 맵의 키인 열거 타입 자체로 출력용 문자열을 제공해 결과에 직접 레이블을 작성하지 않아도 됨
    • 배열 인덱스 계산 과정에서 오류 가능성 원천 보 ㅇ쇄됨
    • ordinal 메서드보다 좋은 이유는 EnumMap 내부로 구현 방식을 숨겨 안전성과 배열의 성능을 얻은 것

    • 코드를 보면 [ Plant.LifeCycle.class ]로 EnumMap이 생성자를 받는데, 이는 받는 키 타입의 Class 객체는 한정적 타입 토큰으로 런타임 제네릭 타입 정보를 제공함 (아이템 33)

스트림을 사용하면 코드를 더 줄일 수 있음

  • 코드 37-3 참고
    • 고유 맵 구현체를 사용해 EnumMap을 사용해 얻는 공간과 성능의 이점이 사라짐
    • EnumMap을 이용해 데이터와 열거 타입을 매핑하여 최적화를 시킬 수 있음 [코드 37-4]
  • 스트림 사용으로 인한 EumMap과의 차이
    • EnumMap은 언제나 식물 생애주기당 하나씩의 중첩 맵을 만듦
    • Stream은 해당 생애주기에 속하는 식물이 있을 때만 만듦
  • 다시 한 번 더 이야기하지만 ordinal을 사용하면 배열 인덱스의 관계를 알 수 없으며, IndexOutOfBoundsException, NullPointerException이 발생함. 아니 그냥 이상하게 동작할 수 있음.

    인덱스가 잘 못 나오면 그냥 엉뚱한거 쭉 해버리는 거임.

  • 위와 같은 이유로 EnumMap을 사용하는게 훨씬 좋음. [코드 37-6]
    • 전이 하나를 얻기 위해 이전 상태와 이후 상태가 필요하니 맵 두개를 중첩해 쉽게 해결 가능함
    • 내부 맵은 이전 상태 전이를 연결하고
      외부 맵은 이후 상태와 안쪽 맵을 연결함

      액체가 녹았다가 얼을 때, 그걸 생각하면 됨. 고체 -> 액체, 액체 -> 고체 변하듯 맵도 이전 상태와 이후 상태를 연결해 주어야 함.

    • (무슨 말인지 다시 이해를 해야겠으나..) 전이 전후의 두 상태를 전이 열거 타입 Transition으로 입력 받아, 이 Transition 상수들로 중첩된 EnumMap을 초기화하면 됨

  • 코드 37-6
    • Map<Phase, Map<Phase, Transition>>
      • 이전 상태에서 이후 상태에서 전이로의 맵에 대응
      • 한 마디로.. 이전 상태를 이후 상태로 대응 시켜라 맞춰라 그런 뜻
      • 여기서 java.util.stream.Collector 두 개를 차례로 사용
        • groupingBy ==> 전이를 이전 상태 기준으로 묶음
        • toMap ==> 이후 상태를 전이에 대응시키는 EnumMap 생성
        • 병합 함수인 (x, y) -> y는 선언하고 사용하지는 안흔ㄴ데, 이는 EnumMap을 얻으려면 맵 팩터리가 필요하며, 위 수집기들은 점층적 팩터리를 제공하기 때문
    • 원소 수정이 일어날 경우
      • EnumMap은 상태 목록에 무언가를 추가하고 전이 목록에 추가하면 끝임
      • 배열로 선언했을 때와 다르게 배열로 구현되어 발생하는 낭비된 공간과 시간이 거의 없이 명확하고 안전하게 유지보수가 쉬움

핵심 정리

  • 배열 인덱스를 얻기 위해 ordinal 메서드를 쓰는 것은 일반적으로 좋지 않으니 EnumMap을 사용할 것
  • 다차원 관계는
    • EnumMap< ..., EnumMap<...>> 으로 표현