[Effective Java] 6장 열거 타입과 애너테이션 (1부)


정리
아이템 30. 열거타입은 정수 상수보다 좋다. 대다수의 열거타입이 명시적 생성자나 메서드 없이 쓰이지만, 각 상수를 특정 데이터와 연결짓거나 상수마다 다르게 동작하게 할 때는 필요하다. 드물게 하나의 메서드가 상수별로 다르게 동작해야 할 때도 있다. 이런 열거타입에서는 switch문 대신 상수별 메서드 구현을 사용하자. 열거 타입 상수 일부가 같은 동작을 공유한다면 전략 열거타입 패턴을 사용하자.

아이템 32. 열거할 수 있는 타입을 한데 모아 집합 형태로 사용한다고 해도 비트 필드를 사용할 이유는 없다. EnumSet클래스가 비트 필드 수준의 명료함과 성능을 제공하고 아이템34에서 설명한 열거 타입의 장점까지 갖고 있기 때문이다. EnumSet은 불변으로 만들 수 없지만, Collections.unmodifiableSet으로 EnumSet을 감싸서 사용할 수 있다. (, 명확성과 성능이 조금 희생됨)

아이템 33. 배열의 인덱스를 얻기 위해 ordinal을 쓰는 것을 일반적으로 좋지 않으니, 대신 EnumMap을 사용하라. 다차원 관계는 EnumMap<… , EnumMap<…>>으로 표현하라. 애플리케이션 개발자는 Enum.ordinal을 사용하지 말아야한다.


내용
아이템 34. int 상수 대신 열거 타입을 사용하라
● 열거타입은 일정 개수의 상수 값을 정의한 다음, 그 외의 값은 허용하지 않는 타입이다.
● 정수 열거 패턴 기법에는 단점이 많다.
1. 타입의 안전을 보장할 방법이 없고, 표현력이 좋지 않다.
2. 컴파일하면 그 값이 클라이언트 파일에 그대로 새겨진다. 그래서 상수의 값이 바뀌면 반드시 다시 컴파일을 해야한다.
3. 정수 상수는 문자열로 출력하기 어렵다. 값을 출력하거나 디버거를 살펴보면 단지 숫자로만 보이기 때문이다.
열거 패턴의 단점을 보안하기 위해 열거 타입이 등장했다. 자바의 열거타입은 완전한 형태의 클래스라서 다른 언어의 열거타입보다 강력하다.
열거타입 자체는 클래스이며, 상수 하나당 자신의 인스턴스를 하나씩 만들어 public static final 필드로 공개한다. 열거타입 선언으로 만들어진 인스턴스들은 딱 하나씩만 존재한다.
● 열거타입의 장점은 다음과 같다.
1. 컴파일타임 타입 안전성을 제공한다.
2. 열거타입에는 각자의 이름 공간이 있어서 이름이 같은 상수도 공존이 가능하다.
3. 열거타입에 새로운 상수를 추가하거나 순서를 바꿔도 다시 컴파일하지 않아도 된다.
4. 열거타입의 toString 메서드는 출력하기에 적합한 문자열을 내어준다.
5. 임의 메서드나 필드를 추가할 수 있고 임의의 인터페이스를 구현할 수 있다.
● 열거타입 상수 각각을 특정 데이터와 연결지으려면 생성자에서 데이터를 받아 인스턴스 필드에 저장하면 된다. 근본적으로 불변이라 모든 필드는 final이여야한다.
● 열거타입을 선언한 클래스 혹은 패키지에서만 유용한 기능은 private package-private 메서드로 구현한다.
● 열거타입은 상수별로 다르게 동작하는 코드를 구현하는 apply라는 추상메서드가 있다. apply라는 추상메서드를 선언하고 각 상수별로 클래스 몸체(constant-specific class body), 즉 각 상수에 자신에 맞게 재정의하는 방법이다. 이를 상수별 메서드 구현이라고 한다.
● 상수별 메서드 구현에는 열거타입 상수끼리 코드를 공유하기 어렵다. 그래서 private로 중첩으로 선언하여, 필요한 경우에만 타입을 맞춰 사용하는 것이 좋다.
enum PayrollDay {
        private final PayType payType;
        PayrollDay(PayType payType) {
               this.payType = payType;
        }
        enum PayType {
               WEEKDAY {
               },
               WEEKEND {
               }
        };
}
● 기존의 열거타입에 상수별 동작을 혼합해 넣을 때는 switch문이 좋은 선택이 될 수 있다.
● 필요한 원소를 컴파일타임에 다 알 수 있는 상수 집합이라면 항상 열거타입을 사용하자. 열거타입에 정의된 상수 개수가 영원히 고정 불변일 필요는 없다.
아이템 35. ordinal 메서드 대신 인스턴스 필드를 사용하라
● 열거타입은 해당 상수가 그 열거타입에서 몇 번째 위치인지를 반환하는 ordinal이라는 메서드를 제공한다. 이 메서드는 EnumSet EnuMap 같이 열거타입 기반의 범용 자료구조에 쓸 목적으로 설계되었다. 이런 경우가 아닌 이상 사용하지 말자.
● 열거타입 상수에 연결된 값은 ordinal 메서드로 얻지말고 인스턴스 필드에 저장하자.
아이템 36. 비트 필드 대신 EnumSet을 사용하라
● 비트별 OR를 사용해 여러 상수를 하나의 집합으로 모은 집합이 비트 필드이다.
● 비트 필드를 사용하면 합집합과 교집합 같은 집합연산을 효율적으로 할 수 있지만, 정수 열거 상수의 단점을 그대로 지닌다. 비트 필드 값이 그대로 출력되면 해석하기가 어렵고, 비트 필드 하나에 녹아 있는 모든 원소를 순회하기도 까다롭다. API를 수정하지 않고 비트 수를 더 늘릴 수도 없다.
java.util 패키지의 EnumSet 클래스는 비트 필드를 사용하는 것보다 좋다. 열거타입 상수의 값으로 구성이된 집합을 효과적으로 표현한다. Set인터페이스를 완벽히 구현하며, 타입 안전하고 다른 어떤 Set 구현체와도 함께 사용할 수 있다.
EnumSet의 내부는 비트 벡터로 구현되어 있다. 속도도 빠르고, 비트를 다루면서 생기는 까다로운 작업도 대신 처리해준다. EnumSet을 사용하면 코드가 간결해지고 안전하다.
아이템 37. ordinal 인덱싱 대신 EnumMap을 사용하라
● 배열이나 리스트에서 원소를 꺼낼대 ordinal 메서드로 인덱스를 얻을 때가 있다. 배열은 제네릭과 호환되지 않으니 비검사 형변환을 수행해야 하고 깔끔히 컴파일 되지 않는다. 이유는 정확한 정숫값을 사용한다는 보증을 해야하기 때문이다. (ArrayIndexOutOfBoundsException이 발생할 수 있다.)
● 열거타입을 키로 사용하도록 설계한 아주 빠른 Map 구현체가 존재하는데, EnumMap이다.
● EnumMap을 사용할 경우 짧고 명료하고 안전하고 유지보수성도 좋고 성능도 원래 버전과 동등하다. EnumMap의 성능이 ordinal을 쓴 배열에 비견되는 이유는 내부에서 배열을 사용하기 때문이다. 그래서 낭비되는 공간과 시간이 명확하다.
● 스트림을 사용해 맵을 관리하면 코드를 더 줄일 수 있다. 다음은 앞 예의 동작을 거의 그대로 모방한 가장 단순한 형태의 스트림이다.
System.out.println(Arrays.stream(garden).collect(groupingBy(p -> p.lifeCycle));
기존의 EnumMap은 언제나 생명주기당 하나씩의 중첩 맵을 만들지만 스트림을 사용하면 해당 생명주기에 속한 객체가 있을 때만 만든다.


용어정리
● 정수형 열거 패턴 : 클래스에 final static으로 int 형 상수들을 선언하여 모은 객체를 말한다.



종종 공통점이 있는 상수들을 하나의 클래스로 모아서 관리하게 쉽게 만들려고 할 때가 있다. 그럴때 열거타입을 사용하게 되면 타입 안정성과 수정시 컴파일을 하지 않아도 될 수가 있다. 상수를 용도별로 모아서 사용할 때 적용해보자

댓글