📌 6장 : 열거 타입과 애너테이션
아이템 34. int 상수 대신 열거 타입을 사용하라
- 정수 열거 패턴 단점
- 타입 안전을 보장할 방법이 없고, 표현력이 좋지 않다.
- 문자열 출력이 까다롭다.
- 열거 타입(enum type)
- 열거 타입 자체는 클래스이다.
- 상수 하나당 자신의 인스턴스를 만들어 public static final 필드로 공개한다.
- 밖에서 접근할 수 있는 생성자를 제공하지 않기 때문에 final
- 열거 타입 선언으로 만들어진 인스턴스는 딱 하나씩 존재하는 것을 보장해준다.
아이템 35. ordinal 메서드 대신 인스턴스 필드를 사용하라
- 열거 타입 상수는 하나의 정수값과 대응된다.
- 모든 열거 타입은 해당 상수가 열거 타입에서 몇 번째 위치인지 ordinal이라는 메서드를 제공해준다.
- 잘못된 사용 예시
- 상수 선언 순서가 바뀌면 numberOfMusicians 오동작
- 해결방법 : 열거 타입 상수에 연결된 값은 ordinal 메서드로 얻지 말고 인스턴스 필드에 저장하기
public enum Ensemble {
SOLO, DUET, TRIO, QUARTET, QUINTET,
SEXTET, SEPTET, OCTET, NONET, DECTET;
public int numberOfMusicians() { return ordinal() + 1; }
}
public enum Ensemble {
SOLO(l), DUET(2), TRI0(3), QUARTET(4), QUINTET(5),
SEXTET(6), SEPTET(7), 0CTET(8), D0UBLE_QUARTET(8),
N0NET(9), DECTET(10), TRIPLE_QUARTET(12);
private final int numberOfMusicians;
Ensemble(int size) { this.numberOfMusicians = size; }
public int numberOfMusicians() { return numberOfMusicians; }
}
아이템 36. 비트 필드 대신 EnumSet을 사용하라
- 비트 필드(bit field)
- 비트별 OR을 사용해 여러 상수를 하나의 집합으로 모을 수 있는 집합
- 비트별 연산을 사용해 합집합과 교집합 같은 집합 연산을 효율적으로 수행
- 정수 열거 상수의 단점을 갖고있다.
- java.util 패키지의 EnumSet 클래스는 열거 타입의 상수 값으로 구성되 집합을 효과적으로 표현한다.
public class Text {
public enum Style { BOLD, ITALIC, UNDERLINE, STRIKETHROUGH }
// 어떤 Set을 넘겨도 되나, EnumSetoi 가장 좋다.
public void applyStyles(Set<Style> styles) { ... }
}
아이템 37. ordinal 인덱싱 대신 EnumMap을 사용하라
- 배열이나 리스트에서 원소를 꺼낼 때 ordinal 메서드로 인덱스를 얻는 경우가 있다.
- 동작은 되지만 배열은 제네릭과 호환이 되지 않아 비검사 형변환을 수행해야 하고 깔끔하게 컴파일 되지 않는다.
- EnumMap : 열거 타입을 키로 사용하도록 설계한 Map 구현체
Map<Plant.LifeCycle, Set<Plant» plantsByLifeCycle =
new EnumMapo(Plant.LifeCycle.class);
for (Plant.LifeCycle Ic : Plant.LifeCycle.values())
plantsByLifeCycle.put(lc, new HashSeto());
for (Plant p : garden)
plantsByLifeCycle. get (p.lifeCycle).add(p);
System.out.printIn(plantsByLifeCycle);
- stream과 EnumMap 사용해서 데이터와 열거 타입 매핑
System.out.printIn(Arrays.stream(garden)
.collect(groupingBy(p -> p.LifeCycle,
() -> new EnumMap<>(LifeCycle.class), toSet())));
아이템 38. 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라
- 타입 안전 열거 패턴은 열거 타입과 다르게 확장할 수 있다.
- 기본 연산 외에 사용자 확장 연산을 추가해야 하는 경우
- 연산 코드용 인터페이스를 정의하고 열거 타입이 인터페이스를 구현하게 한다.
- 하지만 열거 타입끼리 구현을 상속할 수 없다.
public interface Operation {
double apply(double x, double y);
}
public enum BasicOperation implements Operation {
PLUS("+") {
public double apply(double x, double y) {return x + y; }
MINUS("-") {
public double apply(double x, double y) {return x - y; }
TIMES("*") {
public double apply(double x, double y) {return x * y; }
DIVIDE("/") {
public double apply(double x, double y) {return x / y; }
};
private final String symbol;
BasicOperation(String symbol) {
this.symbol = symbol;
}
@Override public String toString() {
return symbol;
}
}
아이템 39. 명명 패턴보다 애너테이션을 사용하라
- 명명 패턴의 단점
- 실수로 이름을 tsetSafety Override로 지으면 JUnit 3이 메서드를 무시하고 지나치고 테스트가 통과한 것으로 오해할 수 있다.
- 올바른 프로그램 요소에서만 사용된다는 보증이 없다.
- 프로그램 요소를 매개변수로 전달할 방법이 없다.
- 애너테이션
- JUnit 4에서 도입
- marker 애너테이션 타입 선언 예시
import j;*ava.lang.annotation.*;
/**
* 테스트 메서드임을 선언하는 애너테이션이다.
* 매개변수 없는 정적 메서드 전용이다.
*/
@Retention (Retent ionPol.icy .RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
}
- 메타애너테이션(meta-annotation) : 애터테이션 선언에 있는 애너테이션
- @Test가 런타임에도 유지되어야 한다는 표시
아이템 40. @Override 애너테이션을 일관되게 사용하라
- 상위 클래스의 메서드를 재정의하려는 모든 메서드에 @Override 애너테이션을 달아야 한다.
- 예외 : 구체 클래스에서 상위 클래스의 추상 메서드를 재정의 하는 경우
아이템 41. 정의하려는 것이 타입이라면 마커 인터페이스를 사용하라
- 마커 인터페이스(marker interface) : 아무 메서드를 담고 있지 않고, 자신을 구현하는 클래스가 특정 속성을 가진 것을 표현해주는 인터페이스
- ex) Serializable : 자신을 구현한 클래스의 인스턴스를 직렬화(serialization)할 수 있도록 알려준다.
- 마커 인터페이스와 마커 애너테이션
- 마커 인터페이스는 구현한 클래스의 인스턴스들을 구분하는 타입으로 쓸 수 있다.
- 마커 애너테이션은 그렇지 않다.
- 마커 인터페이스는 적용 대상을 더 정밀하게 지정할 수 있다.
- 마커 애너테이션은 거대한 애너테이션 시스템의 지원을 받는다.
- 활용
- 클래스와 인터페이스 외의 프로그램 요소에 마킹하는 경우 -> 마커 애너테이션 사용
- 새로 추가하는 메서드 없이 타입 정의가 목적인 경우 -> 마커 인터페이스 사용
- 자바의 직렬화
- Serializable 마커 인터페이스를 보고 직렬화가 가능한 타입인지 확인