37. ordinal 인덱싱 대신 EnumMap을 사용하라
아이템 37. ordinal 인덱싱 대신 EnumMap을 사용하라
핵심 요약
enum 상수에 따른 값들을 배열에 저장하고 ordinal() 메서드가 반환하는 정수 값을 인덱스로 사용하려는 유혹이 있을 수 있습니다.
하지만 이는 타입 안전성이 없고 유지보수에 취약한 방식입니다.
대신 enum을 키로 사용하도록 특별히 설계된 EnumMap 을 사용하면 타입 안전성, 명확성, 유지보수성, 그리고 우수한 성능까지 모두 확보할 수 있습니다.
기존 방식: ordinal()을 배열 인덱스로 사용
ordinal()을 배열 인덱스로 사용
정의
enum상수가 정의된 순서를 반환하는ordinal()메서드ordinal()결과를 배열의 인덱스로 직접 사용하는 방식특정
enum상수에 해당하는 데이터를 배열의 특정 위치에 저장하거나 조회할 때 사용
예시 코드
특징
enum상수와 배열 인덱스의 간단한 연결배열을 사용하므로 특정 위치 빠른 접근
단점
타입 안전성 부재
ordinal()은int를 반환 -> 배열에는 어떤 정수 값이든 인덱스로 사용 가능컴파일러는 그 정수가 유효한
ordinal값인지 보장 X잘못된 정수 사용 시 런타임 오류 발생 가능성
ArrayIndexOutOfBoundsException
유지보수 취약성
enum에 상수 추가 or 순서 변경 ->ordinal()값들이 달라져 코드가 오동작 or 예외 발생배열 크기를 수동으로 관리
enum변경 시 배열 관련 로직 전체 점검 및 수정해야 할 위험 ↑
가독성 저하
숫자로부터 enum 상수 유추의 어려움
출력이나 로깅 시,
ordinal값을 다시enum상수로 변환하는 번거로운 과정 필요
제네릭과의 비호환성
배열은 제네릭과 잘 맞지 않아 비검사 형변환(
(Set<Plant>[]))과 컴파일 경고 유발배열은 각 인덱스의 의미를 모르니 출력 결과에 직접 레이블 작성
개선된 방식: EnumMap 사용
EnumMap 사용
정의
enum타입을 키로 사용하도록 최적화된 고성능Map구현체ordinal()인덱싱을 대체하는 가장 이상적인 현대적 대안java.util패키지
예시 코드
특징
키로 사용할
enum의Class객체(타입 토큰)를 인자로 받아 맵 초기화내부적으로 배열을 사용하여 데이터를 저장
->
ordinal()을 직접 사용하는 것과 비견될 만한 성능 제공
Map인터페이스를 구현하여 기존 컬렉션 프레임워크와 완벽하게 호환맵의 키인 열거 타입이 그 자체로 출력용 문자열 제공
장점
타입 안전성 보장
키는 명시된
enum타입만 가능, 값은 해당enum상수컴파일 시점에 타입 오류 검출
런타임 오류 발생 가능성 원천적으로 차단
ArrayIndexOutOfBoundsException
유지보수 용이성
enum에 상수 추가 및 순서 변경 시EnumMap코드 영향없음배열 크기 관리 필요 X
가독성 향상
훨씬 명확하고 직관적인 코드
enum상수 자체가 키로 사용되므로, 출력 및 디버깅 시 의미를 바로 파악할 수 있습니다.
고성능
내부 구현 최적화 덕분에 일반
HashMap보다 빠름ordinal()인덱싱 방식과 성능 차이가 거의 없음
안전하고 편리한 사용
복잡하고 오류 가능성이 있는
ordinal()및 배열 인덱스 계산 로직을 작성할 필요 없음비검사 형변환이 필요 없어 코드가 깔끔하고 안전
단점
굳이 따지자면
Map객체 생성 및 메서드 호출 오버헤드가 이론적으로 존재내부 최적화로 인해 실제 성능 차이는 미미
안전성과 유지보수성 이점이 이를 압도
추가 고려 사항
스트림 활용
StreamAPI의Collectors.groupingBy와Collectors.toSet을EnumMap생성자 팩토리와 함께 사용하면EnumMap을 더 간결하게 생성하고 초기화 가능
다차원 매핑
두 개의
enum값에 따라 데이터를 매핑해야 할 때,ordinal()을 이중으로 사용하는 배열은 앞서 언급한 모든 단점을 증폭컴파일러는 ordinal과 배열 인덱스의 관계를 모른다
Phase나 Phase.Transition 열거 타입을 수정한다면
상전이 표 TRANSITIONS를 함께 수정하지 않거나 실수로 잘못 수정하면 런타임 오류 발생
상전이 표의 크기는 상태의 가짓수가 늘어나면 제곱해서 커지며 null로 채워지는 칸도 늘어날 것
런타임에 NullPointerException을 일으키는 안 좋은 습관
이 경우 중첩
EnumMap(EnumMap<Enum1, EnumMap<Enum2, Value>>) 을 사용하는 것이 훨씬 안전하고 유연하며 관리하기 쉬운 해결책.새로운 상태 추가 시, 상태 목록에 추가하고, 전이 목록에 전이 상태만 추가
나머지는 기존 로직에서 잘 처리해주어 잘못 수정할 가능성이 극히 낮음
실제 내부에서는 맵들의 맵이 배열들의 배열로 구현되어 낭비되는 공간과 시간도 거의 없음
명확하고 안전하고 유지보수하기 좋음
핵심 정리
배열의 인덱스를 얻기 위해 ordinal을 쓰는 것은 일반적으로 좋지 않으니, 대신 EnumMap을 사용하라.
다차원 관계는 EnumMap<..., EnumMap<...>>으로 표현하라
"애플리케이션 프로그래머는 Enum.ordinal을 (웬만해서는) 사용하지 말아야 한다(item35)" 는 일반 원칙의 특수한 사례이다.
Last updated