본문 바로가기
카테고리 없음

열거타입과 애너테이션

by y.j 2022. 7. 17.
728x90

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);

 

728x90

댓글