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

2022. 10. 25. 22:58· BE/Java
목차
  1. 아이템 37. ordinal 인덱싱 대신 EnumMap을 사용하라
  2.  
  3. 정리
반응형

EFFECTIVE JAVA(이펙티브 자바)

 

 

이 포스팅에서 작성하는 내용은 EFFECTIVE JAVA(이펙티브자바) 에서 발췌하였습니다.


아이템 37. ordinal 인덱싱 대신 EnumMap을 사용하라

 

ordinal 메서드

ex 1) Plant 클래스

public class Plant {
    enum LifeCycle { ANNUAL, PERNNIAL, BIENNIAL}

    final String name;
    final LifeCycle lifeCycle;

    Plant(String name, LifeCycle lifeCycle) {
        this.name = name;
        this.lifeCycle = lifeCycle;
    }

    @Override
    public String toString() {
        return name;
    }
}
  • 정원에 심은 식물들을 배열 하나로 관리하고 이들을 생애주기별로 묶어보는 메서드를 만들어보자.
  • 생애주기 별로 총 3개의 집합을 만들고 정원을 한 바퀴 돌며 각 식물을 해당 집합에 넣는다.

 

ex 2) ordinal()을 배열 인덱스로 사용하는 코드 (안좋은 예)

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", LifeCycle.values()[i], plantsByLifeCycle[i]);
}
  • 동작하지만 문제가 많은 코드이다.
  • 배열은 제네릭과 호환되지 않으니 비검사 형변환을 수행해야 하고 깔끔하게 컴파일 되지 않을 것이다.
  • 배열은 각 인덱스의 의미를 모르니 출력 결과에 직접 레이블을 달아야한다.
  • 가장 큰 문제는 정확한 정숫값을 사용한다는 것을 직접 보증해야한다는 것이다.
  • 정수는 열거 타입과 달리 타입 안전하지 않기 때문이다.
  • EnumMap을 이용해 해결할 수 있다.

 

ex 3) EnumMap을 사용해 데이터와 열거타입을 매핑한 코드

Map<LifeCycle, Set<Plant>> plantsByLifeCycle = new EnumMap<>(LifeCycle.class);

for (LifeCycle lifeCycle : LifeCycle.values()) {
    plantsByLifeCycle.put(lifeCycle, new HashSet<>());
}
for (Plant p : garden) {
    plantsByLifeCycle.get(p.lifeCycle).add(p);
}
System.out.println(plantsByLifeCycle);
  • EnumMap은 열거 타입을 키로 사용하도록 설계한 Map 구현체이다.
  • 안전하지 않은 형변환은 쓰지 않고, 맵의 키인 열거 타입이 그 자체로 출력용 문자열을 제공하니 출력 결과에 직접 레이블을 달 일도 없다.
  • 배열 인덱스를 계산하는 과정도 필요 없어 오류가 발생할 확률이 낮아진다.
  • EnumMap의 생성자가 받는 키 타입의 Class 객체는 한정적 타입 토큰으로, 런타임 제네릭 타입 정보를 제공한다.

 

 

ex4) 스트림을 사용한 코드 (EnumMap을 사용하지 않은 예)

System.out.println(Arrays.stream(garden).collect(groupingBy(p -> p.lifeCycle)));
  • 이 코드는 EnumMap이 아닌 고유한 맵 구현체를 사용했기 때문에 EnumMap을 써서 얻은 공간과 성능 이점이 사라진다는 문제가 있다.

 

ex 5) 스트림을 사용한 코드 (EnumMap을 사용한 예)

System.out.println(Arrays.stream(garden).collect(groupingBy(p -> p.lifeCycle, 
											() -> new EnumMap<>(LifeCycle.class), toSet())));
  • 스트림을 사용하면 EnumMap만 사용했을 때와는 살짝 다르게 동작한다.
  • EnumMap 버전은 언제나 식물의 생애주기당 하나씩의 중첩 맵을 만들지만, 스트림 버전에서는 해당 생애주기에 속하는 식물이 있을 때만 만든다.

 

 

 

ex 6) 배열들의 배열 인덱스에 ordinal() 사용 (안좋은 예)

public enum Phase {
    SOLID, LIQUID, GAS;

    public enum Transition {
        MELT,FREEZE, BOIL, CONDENSE, SUBLIME, DEPOSIT;

				// 행은 from의 ordinal, 열은 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()];
        }
    }
}
  • 위의 코드는 작동하지만 좋지 않다.
  • Phase나 Phase.Transition 열거 타입을 수정하면 원하는 동작이 일어나지 않는다.
  • 또한, TRANSITIONS 배열의 크기는 상태의 가짓수가 늘어나면 제곱해서 커지며 null로 채워지는 칸도 많아질 것이다.
  • 즉, 이 경우에도 EnumMap을 사용하는 것이 좋다.

 

 

 

ex 7) 중첩 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;
        }

				// 전이 Map 을 초기화함
        private static final Map<Phase, Map<Phase, Transition>> m = Stream.of(values())
                .collect(Collectors.groupingBy(t -> t.from, 
                        () -> new EnumMap<>(Phase.class), 
                        Collectors.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);
        }
    }

}
  • 전이 하나를 얻기 위해 맵 2개를 중첩한 코드 즉, 안쪽 맵은 이전 상태와 전이를 연결하고 바깥 맵은 이후 상태와 안쪽 맵을 연결한 것
  • 전이 전후의 두 상태를 전이 열거 타입 Transition의 입력으로 받아, 이 Transition 상수들로 중첩된 EnumMap을 초기화 하면 된다.

 

 

ex 8) 새로운 상태인 플라스마가 추가된 코드

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),
				IONIZE(GAS, PLASMA          DEIONIZE(PLASMA, GAS);

					... // 나머지 코드 그대로
		}
}
  • 기존 로직에서 잘 처리해뒀기 때문에 단순히 Phase에 상태 추가 및 Phase.Transition에 전이 상태 2개를 추가하면 된다.

 

 

정리

  • 배열의 인덱스를 얻기 위해 ordinal을 쓰는 것은 일반적으로 좋지 않으니, EnumMap을 사용하자
반응형

'BE > Java' 카테고리의 다른 글

[Effective Java] 아이템 39. 명명 패턴보다 애너테이션을 사용하라  (0) 2022.10.27
[Effective Java] 아이템 38. 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라  (0) 2022.10.26
[Effective Java] 아이템 36. 비트 필드 대신 EnumSet을 사용하라  (0) 2022.10.24
[Effective Java] 35. ordinal 메서드 대신 인스턴스 필드를 사용하라  (0) 2022.10.24
[Effective Java] 34. int 상수 대신 열거 타입을 사용하라  (0) 2022.10.21
  1. 아이템 37. ordinal 인덱싱 대신 EnumMap을 사용하라
  2.  
  3. 정리
'BE/Java' 카테고리의 다른 글
  • [Effective Java] 아이템 39. 명명 패턴보다 애너테이션을 사용하라
  • [Effective Java] 아이템 38. 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라
  • [Effective Java] 아이템 36. 비트 필드 대신 EnumSet을 사용하라
  • [Effective Java] 35. ordinal 메서드 대신 인스턴스 필드를 사용하라
멍목
멍목
개발 관련 새롭게 알게 된 지식이나 좋은 정보들을 메모하는 공간입니다.
반응형
멍목
김멍목의 개발블로그
멍목
전체
오늘
어제
  • 분류 전체보기 (514)
    • BE (190)
      • Spring (21)
      • Java (141)
      • Kotlin (6)
      • JPA (22)
    • FE (33)
      • Javascript (16)
      • Typescript (0)
      • React (5)
      • Vue.js (9)
      • JSP & JSTL (3)
    • DB (32)
      • Oracle (22)
      • MongoDB (10)
    • Algorithm (195)
    • Linux (8)
    • Git (6)
    • etc (42)
    • ---------------------------.. (0)
    • 회계 (4)
      • 전산회계 2급 (4)
    • 잡동사니 (2)

블로그 메뉴

  • 홈
  • 관리

공지사항

인기 글

태그

  • JPA 공부
  • MongoDB 기초부터 실무까지
  • 코테공부
  • MongoDB with Node.js
  • 알고리즘공부
  • MongoDB 공부
  • 자기 공부
  • 더 자바 Java 8
  • 알고리즘 공부
  • 프로젝트로 배우는 Vue.js 3
  • 자기 개발
  • 이펙티브 자바
  • Java to Kotlin
  • Effective Java
  • 자바공부
  • 코틀린
  • 이펙티브자바
  • junit5
  • 자기개발
  • 더 자바 애플리케이션을 테스트하는 다양한 방법
  • 자바 공부
  • Oracle
  • 코테 공부
  • java 8
  • vue3 공부
  • 전산회계 2급 준비
  • 자바 개발자를 위한 코틀린 입문
  • JPA
  • 자바 테스팅 프레임워크
  • 자기공부

최근 댓글

최근 글

hELLO · Designed By 정상우.v4.2.0
멍목
[Effective Java] 아이템 37. ordinal 인덱싱 대신 EnumMap을 사용하라
상단으로

티스토리툴바

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.