ordinal 메서드 대신 인스턴스 필드를 사용하라.
Enum의 API 문서를 보면 ordinal은 EnumSet과 EnumMap같이 열거 타입 기반의 범용 자료구조에 쓸 목적으로 설계되었다. 대부분의 프로그래머는 이 메서드를 쓸일이 없다.
public enum Ensemble {
SOLO, DUET, TRIO, QUARTET, QUINTET, SEXTET, SEPTET, OCTET, NONET, DECTET;
public int numberOfMusicians() {
return ordinal()+1;
}
}
위 코드에 DOUBLE_QUARTET을 추가하려고 한다면 8이라는 정수를 갖지 못할 것이다. ordinal()을 사용하지 말고 인스턴스필드에 저장하자.
public enum Ensemble {
SOLO(1), DUET(2), TRIO(3), QUARTET(4), QUINTET(5),
SEXTET(6), SEPTET(7), OCTET(8), DOUBLE_QUARTET(8),
NONET(9), DECTET(10);
private final int numberOfMusician;
Ensemble(int size) {this.numberOfMusician = size;}
public int numberOfMusician() {return numberOfMusician; }
}
비트 필드 대신 EnumSet을 사용하라.
아래 코드는 비트를 사용해서 필드를 구성했다.
public class EnumSet {
public static final int STYLE_BOLD = 1 << 0;
public static final int STYLE_ITALIC = 1 << 1;
public static final int STYLE_UNDERLINE = 1 << 2;
public static final int STYLE_STRIKETHROUGH = 1 << 3;
public void applyStyles(int styles) { ... }
}
비트 필드를 구성의 단점
1. 해석하기 어렵다.
2. 모든 원소를 순회하기도 까다롭다.
3. 몇 비트가 필요한지 미리 예측하여야 한다.
EnumSet은 Set을 상속받아 구현했기 때문에 Set에 타입이 안전하고 다른 어떤 Set 구현체와도 함께 사용 할 수 있다. EnumSet의 내부는 비트 벡터로 되어 있어 비트필드에 비견되는 성능을 보여준다.
public class Text {
public enum Style { BOLD, ITALIC, UNDERLINE, STRIKETHROUGH }
public void applyStyles(Set<Style> styles) { ... }
}
text.applyStyles(EnumSet.of(Text.Style.BOLD, Text.Style.ITALIC));
ordinal 인덱싱 대신 EnumMap을 사용하라.
Plant라는 클래스를 HashMap으로 인덱싱을 해보자.
public 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>[] plantsByLifeCycle =
(Set<Plant>[]) new Set[Plant.LifeCycle.values().length];
for(int i = 0; i < plantsByLifeCycle.length; ++i)
plantsByLifeCycle[i] = new HashSet<>();
for(Plant p : garden)
plantsByLifeCycle[p.lifeCycle.ordinal()].add(p);
for(int i = 0; i < plantsByLifeCycle.length; ++i)
System.out.println("%s: %s%n",
Plant.LifeCycle.values()[i], plantsByLifeCycle[i]);
위 코드의 문제점
1. 배열은 제네릭과 호환되지 않아 비검사 형변환을 수행해야 한다.
2. 배열은 각 인덱스의 의미를 모르니 출력 결과에 직접 레이블을 달아야 한다. 정확한 정숫값을 사용한다는 것을 보증이 되야 한다.
EnumMap도 Map을 구현한 구현체로써 Map과 호환성이 좋다.
Map<Plant.LifeCycle, Set<Plant>> plantsByLifeCycle =
new EnumMap<>(Plant.LifeCycle.class);
for(Plant.LifeCycle lc : Plant.LifeCycle.values())
plantsByLifeCycle.put(lc, new HashSet<>());
for(Plant p : garden)
plantsByLifeCycle.get(p.lifeCycle).add(p);
System.out.println(plantsByLifeCycle);
아래코드처럼 람다와 스트림을 사용하면 좀 더 간략화 할 수 있다. 2개는 무슨 차이가 있을까? EnumMap으로 되어있는 코드는 garden의 모든 필드에 대해서 맵을 만들고, 위 코드는 인스턴스화되어있는 객체만 Map으로 만든다.
System.out.println(Arrays.stream(garden)
.collect(groupingBy(p -> p.lifeCycle)));
System.out.println(Arrays.stream(garden)
.collect(groupingBy(() -> new EnumMap<>(Plant.LifeCycle.class), toSet())));
필드 사이에 전이를 하기 위한 코드
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,
() -> new EnumMap<>(Phase.class),
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);
}
}
}
PLASMA를 추가하더라도 나머지 코드들은 그대로 둘 수 있다.
public enum Phase {
SOLID, LIQUID, GAS, PLASMA;
public enum Transition {
MELT(SOLID, LIQUID), FREEZE(LIQUID, SOLID),
BOIL(LIQUID, GAS), CONDENSE(GAS, LIQUID),
SUBLIME(SOLID, GAS), DEPOSIT(GAS, SOLID),
ISONIZE(GAS, PLASMA), DEIONIZE(PLASMA, GAS);
댓글