-
아이템 37. ordinal 인덱싱 대신 EnumMap을 사용하라.JAVA/Effective java 2021. 3. 28. 16:12
- 배열이나 리스트에서 원소를 꺼낼 때 ordinal 메서드로 인덱스를 얻는 코드가 있다.
- 다음 클래스를 예로 살펴보자.
- 정원에 심은 식물을 배열 하나로 관리하고, 이들을 생애주기 (한해살이, 여러해살이, 두해살이) 별로 묶어보자.
- 생애주기별로 총 3개의 집합을 만들고 정원을 한 바퀴 돌며 각 식물을 해당 집합에 넣는다.
- 이때 집합들을 배열 하나에 넣고 생애주기의 ordinal 값을 그 배열의 인덱스로 사용한 예를 봐보자.
- 동작은 하지만 문제가 많다.
- 배열을 제네릭과 호환되지 않으니 비검사 형 변환을 수행해야 하고 깔끔히 컴파일되지 않을 것이다.
- 배열은 각 인덱스의 의미를 모르니 출력 결과에 직접 레이블을 달아야 한다.
- 가장 심각한 문제는 정확한 정숫값을 사용한다는 것을 직접 보증해야 한다는 점이다.
- 정수는 열거타입과 달리 타입 안전하지 않기에 잘못된 동작을 그냥 수행하거나 Exception을 던질 것이다.
- 사실 열거 타입을 키로 사용하도록 설계한 아주 빠른 Map 구현체가 존재하는데, 바로 EnumMap이 그 주인공이다.
- 다음은 EnumMap을 사용해 개선한 코드다.
- 더 짧고 명료하며 안전하고 성능도 원래 버전과 비등하다.
- 안전하지 않은 형변환은 쓰지 않고, 맵의 키인 열거 타입이 그 자체로 출력용 문자열을 제공하니 출력 결과에 직접 레이블을 달 일도 없다.
- 또한 배열 인덱스를 계산하는 과정에서 오류가 날 가능성도 봉쇄된다.
- EnumMap의 성능이 ordinal을 쓴 배열에 비견되는 이유는 그 내부에서 배열을 사용하기 때문이다.
- 내부 구현 방식을 안으로 숨겨서 Map의 타입 안전성과 배열의 성능을 모두 얻어낸 것이다.
- 여기서 EnumMap의 생성자가 받는 키 타입의 Class 객체는 한정적 타입 토큰으로, 런타임 제네릭 타입 정보를 제공한다.
- 스트림을 사용해 맵을 관리 하면 코드를 더 줄일 수 있다.
- 이 코드는 앞선 정원 예제와 마찬가지로 컴파일러는 ordinal과 배열 인덱스의 관계를 알 도리가 없다.
- 즉 Phase나 Phase.Transition 열거 타입을 수정하면서 상전이 표 TRANSITIONS를 함께 수정하지 않거나 실수로 잘못 수정하면 런타임 오류가 날 것이다.
- Array IndexOutOfBoundsException 이나 NullPointerException을 던질 수도 있고 예외도 던지지 않고 이상하게 동작할 수도 있다.
- 다시 이야기하면 EnumMap을 사용하는 편이 훨씬 낫다.
- 전이 하나를 얻으려면 이전 상태(from)와 이후 상태(to)가 필요하니, 맵 2개를 중첩하면 쉽게 해결할 수 있다.
- 안쪽 맵은 이전 상태와 전이를 연결하고 바깥 맵은 이후 상태와 안쪽 맵을 연결한다.
- 전이 이후의 두 상태를 전이 열거 타입 Transition의 입력으로 받아, 이 Transition 상수들로 중첩된 EnumMap을 초기화하면 된다.
- 이제 새로운 플라스마(PLASMA)를 추가해보자.
- 이 상태와 연결된 전이는 2개다. 첫 번째는 기체에서 플라스마로 변하는 이온화(IONIZE) 이고, 둘째는 플라스마에서 기체로 변하는 탈이온화(DEIONIZE) 이다.
- EnumMap 버전에는 상태 목록에 PLASMA를 추가하고 전이 목록에 IONIZE(GAS, PLASMA)와 DEIONIZE(PLASMA, GAS)만 추가하면 끝이다.
핵심 정리
- 배열의 인덱스를 얻기 위해 ordinal을 쓰는 것은 일반적으로 좋지 않으니, 대신 EnumMap을 사용하라.
- 다차원 관계는 EnumMap<..., EnumMap<...>> 로 표현하라.
'JAVA > Effective java' 카테고리의 다른 글
아이템39. 명명 패턴보다 에너테이션을 사용하라 (0) 2021.04.16 아이템 38. 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라 (0) 2021.04.14 아이템36. 비트 필드 대신 EnumSet을 사용하라 (0) 2021.03.26 아이템 35. ordinal 메서드 대신 인스턴스 필드를 사용하라 (0) 2021.03.26 아이템34. int 상수 대신 열거 타입을 사용하라 (0) 2021.03.23