맞는데 왜 틀릴까..?

Java/Effective Java

[Effective Java] Item 37. ordinal 인덱싱 대신 EnumMap을 사용하라

안도일 2023. 6. 29. 13:29

 

Ordinal

 

ordinal은 Enum 타입의 객체에서 사용되는 메서드로 Enum 상수가 정의된 순서를 반환한다.

Enum 상수는 0부터 시작하는 정수값을 가지며, 상수가 정의된 순서대로 0부터 1씩 증가한다.

 

 

해당 예제는 Weekday라는 Enum 타입을 정의하였으며, ordinal 메서드를 사용하여 WEDNESDAY 상수의 순서를 반환해 2를 출력한다.

 

 


ordinal()을 배열 인덱스로 사용 시 문제점

 

이처럼 배열이나 리스트에서 원소를 꺼낼 때 ordinal 메서드로 인덱스를 얻는 코드가 있다.

 

 

해당 코드는 집합들을 배열하나에 넣고 enum의 ordinal 값을 그 배열의 인덱스로 사용하는 경우이다.

 

  • 배열은 제네릭과 호환되지 않으니 비검사 형변환을 수행해야 한다.
  • 배열은 인덱스의 의미를 모르니 출력 결과에 직접 레이블을 달아야 한다.
  • 정확한 정숫값을 사용한다는 것을 직접 보증해야 한다. (Plant.LifeCycle.values().length가 정확한 정숫값이어야 한다.)

 

비검사 형변환
자바에서는 제네릭 배열 생성이 허용되지 않음.
제네릭 타입은 컴파일 시에 타입 안전성을 보장하기 위해 사용하는데, 배열은 타입 안전성을 제공하지 못한다. 따라서 배열을 생성할 때 제네릭 타입으로 형변환을 시도하면 컴파일러는 경고를 발생시킴.
이때 @SuppressWarnings("unchecked") 애너테이션을 사용해 비검사 형변환을 할 수 있다.

 

열거 타입은 컴파일 시에 열거 상수의 유효성을 검사하고, 열거 상수 사이의 형변환을 엄격하게 제한한다.

 

 

EnumMap

 

위 문제 있는 코드를 열거 타입을 키로 사용하도록 설계한 아주 빠른 Map 구현체 EnumMap으로 바꿔보자

 

 

  • 안전하지 않은 형변환 사용 x
  • 맵의 키인 열거 타입이 그 자체로 출력용 문자열 제공
  • 배열 인덱스를 계산하는 과정에서 오류가 날 가능성도 x
  • EnumMap은 내부에서 배열을 사용하므로 Map의 타입 안전성과 배열의 성능을 모두 가짐

EnumMap의 생성자가 받는 키 타입의 Class 객체는 한정적 타입 토큰으로, 런타임 제네릭 타입 정보를 제공한다.

 

 

다차원 관계

 

이전 상태와 이후 상태를 얻기 위해 종종 2차원 배열을 선언하곤 했지만 이제는 EnumMap 2개를 중첩해서 쉽게 해결하자

 

 

 

  • 이처럼 Map을 중첩시켜 실제 내부에서는 맵들의 맵이 배열들의 배열로 구현되어 낭비되는 공간과 시간도 없이 명확하고 안전하게 사용할 수 있다. 
  • 새로운 from - to 관계를 추가하고 싶다면 2차원 배열을 사용했다면 3x3 배열을 4x4배열로 교체해야 했지만 Map 중첩에서는 간단하게 IONIZE(GAS, PLASMA), DEIONIZE(PLASMA, GAS)만 추가하면 된다.

 

 

결론

 

  • 배열의 인덱스를 얻기 위해 ordinal을 쓰는 것은 좋지 않으니 EnumMap을 사용하자
  • 다차원 관계는 EnumMap의 중첩으로 표현하자