작성자: 프람
작성일시: 2024.05.16
내용: Effective Java 3/E 아이템-37
Ordinal 인덱싱
Ordinal이란 Enum class의 상수 선언 순서에 따른 값을 반환해주는 메서드입니다.
더 정확하게는 API 명세를 봅시다.
요약하자면, 첫 번째 상수는 0으로 시작하고 그 뒤로 1씩 증가하여 반환한다고 합니다.
그런데 특이한것은 "대부분의 프로그래머는 이 기능을 사용하지 않는다고 합니다. 단, EnumSet, EnumMap과 같은 곳에서 사용된다고합니다.
이제 부터 왜 대부분의 프로그래머가 Ordinal 메서드를 사용하지 않는지 코드로 알아봅시다.
-예시 객체-
class Plant {
enum LifeCycle { ANNUAL, PERENNIAL, BIENNIAL }
final String name;
final LifeCycle lifeCycle;
Plant(String name, LifeCycle lifeCycle) {
this.name = name;
this.lifeCycle = lifeCycle;
}
@Override
public String toString() {
return name;
}
}
-클라이언트 코드🤮-
Set<Plant> garden
= new HashSet<>(List.of(new Plant("a", LifeCycle.ANNUAL), new Plant("b", LifeCycle.PERENNIAL)));
Set<Plant>[] plantsByLifeCycle =(Set<Plant>[]) new Set[LifeCycle.values().length];
for (int i = 0; i < plantsByLifeCycle.length; i++) {
plantsByLifeCycle[i] = new HashSet<>();
}
for (Plant plant : garden) {
plantsByLifeCycle[plant.lifeCycle.ordinal()].add(plant);
}
for (int i = 0; i < plantsByLifeCycle.length; i++) {
System.out.printf("%s: %s%n",
Plant.LifeCycle.values()[i], plantsByLifeCycle[i]);
}
}
EnumMap - 예시1
문제점
- 타입 안전하지 않다
- OCP 위반
- ArrayIndexOutOfBoundsException
딱 봐도 사용하지 말아야 겠죠??
더 나은 대안은 무엇이 있을까요?
아래와 같이 EnumMap을 사용해 데이터와 열거 타입을 매핑한다.
-개선된 클라이언트 코드👍-
Set<Plant> garden = new HashSet<>(
List.of(new Plant("a", LifeCycle.ANNUAL), new Plant("b", LifeCycle.PERENNIAL)));
Map<LifeCycle, Set<Plant>> plantsByLifeCycle = new EnumMap<>(LifeCycle.class);
for (LifeCycle lifeCycle : LifeCycle.values()) {
plantsByLifeCycle.put(lifeCycle, new HashSet<>());
}
for (Plant plant : garden) {
plantsByLifeCycle.get(plant.lifeCycle).add(plant);
}
System.out.println(plantsByLifeCycle);
System.out.println(garden
.stream().collect(groupingBy(p -> p.lifeCycle, () -> new EnumMap<>(LifeCycle.class), toSet())));
}
EnumMap을 사용해 데이터와 열거 타입을 매핑한다.
EnumMap의 성능은 ordinal을 통한 배열 접근과 비슷한 성능을 보이는데, 이는 내부에서 배열을 래핑하고 있기 때문이다.
이로써 안전성과 성능 두 마리 토끼를 모두 잡을 수 있게 되었다.
EnumMap 예시-2
ordinal 사용🤮
public enum Phase {
SOLID, LIQUID, GAS;
public enum Transition {
MELT, FREEZE, BOIL, CONDENSE, SUBLIME, DEPOSIT;
// 행은 from의 oridinal을 , 열은 to의 ordinal을 인덱스로 쓴다.
private static final Transition[][] TRANSITIONS = {
{null, MELT, SUBLIME},
{FREEZE, null, BOIL},
{DEPOSIT, CONDENSe, null}
};
// 한 상태에서 다른 상태로의 전이를 반환한다.
public static Transition from(Phase from, Phase to) {
return TRANSITIONS[from.ordinal()][to.ordinal()];
}
}
어떤가요?? 이전 ordinal 예시와 같이 똑같은 문제점들이 보이시나요?
- IndexOutOfBoundsException
- NullPointerException
- OCP 위반
EnumMap사용👍
public enum Phase {
SOLID, LIQUID, GAS;
public enum Transition {
MELT(SOLID, LIQUID), FREEZE(LIQUID, SOLID),
BOIL(LIQUID, GAS), CONDENSE(GAS, LIQUID),
SUBLIME(SOLID, GAS), DEPOSIT(GAS, SOLID);
private final Phase from;
private final Phase to;
Transition(Phase from, Phase to) {
this.from = from;
this.to = to;
}
// 상전이 맵을 초기화한다.
private static final Map<Phase, Map<Phase, Transition>> m
= Stream.of(values()).collect(groupingBy(t -> t.from,
toMap(t -> t.to, t -> t,
(x,y) -> y, () -> new EnumMap<>(Phase.class))));
public static Transition from(Phase from, Phase to) {
return m.get(from).get(to);
}
}
결론
ordinal은 절대 사용하지말고, EnumMap, EnumSet으로 사용하자 !!
'독서 감상문 > Effective Java' 카테고리의 다른 글
박싱된 기본 타입보다는 기본 타입을 사용하라 (0) | 2024.06.07 |
---|---|
비트 필드 대신 EnumSet을 사용하라 (0) | 2024.05.16 |
배열보다는 리스트를 사용하라 (0) | 2024.05.09 |
비검사 경고를 제거하라 (0) | 2024.05.06 |
로 타입은 사용하지 말라 (0) | 2024.05.06 |