자바에서 열거 타입을을 제공하기 전에 정수 상수를 한 묶을 선언해서 사용했다.
public static final int A = 1;
public static final int B = 1;
public static final int C = 1;
위와 같은 열거 패턴의 단점
- 타입 안전을 보장하는 방법이 없으며 표현력도 좋지 않다.
- A 라는 상수를 사용해야하는 메서드에 B 라는 상수를 사용해도 컴파일러는 해줄 수 있는게 없다.
- 동등성을 검증할 때도 경고 메시지를 출력하지 않는다.
- 상수의 값이 바뀌면 클라이언트도 반드시 다시 컴파일 해야한다.
- 상수의 갯수를 파악하기 어렵다.
- 문자열을 상수로 만들 경우 하드 코딩하게 만들기 때문에 안정적이지 못하다.
단점을 보완하기 위해 열거타입이 제공된다.
public enum SampleEnum {
A,B,C,D
}
열거 타입 특징
- 인스턴스를 통제
- 만들어진 인스턴스들은 딱 하나씩만 존재한다. → final을 보장한다.
- 각각의 이름 공간이 이 있음
- 싱글턴은 원소가 하나뿐인 열거 타입, 거꾸로 열거 타입은 싱글턴을 일반화한 형태
- 열거 타입에 선언된 상수들의 값을 배열에 담아 반환하는 메서드 values()를 제공한다.
- 클라이언트에 노출해야 할 이유가 없으면 해당 패기지에서만 사용하는 기능이라면 private 나 default 로 접근 제어자 사용
열거 타입 장점
- 컴파일타임 Type Safe
- 기존의 열거 타입 패턴보다 추상적으로 구상 할 수 있다.
- 메서드나 필드를 추가
- 임의의 인터페이스를 구현
열가 타입 상수 각각을 특정 데이터와 연결하려면 생성자에서 데이터를 받아 인스턴스 필드에 저장하면 된다.
public enum SampleEnum {
A(1),
B(2),
C(3),
D(4);
private final int num;
SampleEnum(int num) {
this.num = num;
}
public int getNum() {
return num;
}
}
연산 열거 타입
public enum Operation {
PLUS,MINUS,TIMES,DIVIDE;
public double apply(double x, double y) {
switch (this){
casePLUS: return x + y;
caseMINUS: return x - y;
caseTIMES: return x * y;
caseDIVIDE: return x / y;
}
throw new AssertionError("알 수 없는 연산 : " + this);
}
}
위의 연산 열거 타입은 동작은 한다.
문제점
- 새로운 상수를 추가하면 switch문에도 로직을 추가해야 한다
apply() 메서드를 추상메서드로 작성하고 각 상수에 오버라이드 해서 기능을 구현
public enum OperationSolution1 {
PLUS{
@Override
public double apply(double x, double y) {
return x + y;
}
},
MINUS{
@Override
public double apply(double x, double y) {
return x - y;
}
},
TIMES{
@Override
public double apply(double x, double y) {
return x * y;
}
},
DIVIDE{
@Override
public double apply(double x, double y) {
return x / y;
}
};
public abstract double apply(double x, double y);
}
이러한 구현 방법의 문제점
- 새로운 상수를 추가할 때 apply() 를 재정의 해야함 → 재정의 하지 않는 경우 컴파일 에러
상수별 메서드 구현을 데이터와 결합하여 구현
public enum OperationSolution2 {
PLUS("+") {
@Override
public double apply(double x, double y) {
return x + y;
}
},
MINUS("-") {
@Override
public double apply(double x, double y) {
return x - y;
}
},
TIMES("*") {
@Override
public double apply(double x, double y) {
return x * y;
}
},
DIVIDE("/") {
@Override
public double apply(double x, double y) {
return x / y;
}
};
private final String symbol;
OperationSolution2(String symbol) {
this.symbol = symbol;
}
@Override
public String toString() {
return symbol;
}
public abstract double apply(double x, double y);
}
열거 타입의 상수 이름을 받아 해당 상수를 반환해주는 valueOf() 제공
double apply = OperationSolution2.valueOf("PLUS").apply(1, 14);
toString을 재정의 하려거든 formString 메서드도 함계 제공하는 걸 고려하자
private static final Map<String, OperationSolution2> stringToEnum =
Stream.of(values()).collect(Collectors.toMap(Object::toString, oper -> oper));
public static Optional<OperationSolution2> formString(String symbol) {
return Optional.ofNullable(stringToEnum.get(symbol));
}
전략 열거 타입 패턴
public enum OperationType {
PLUS1(PLUS),
MINUS1(MINUS),
TIMES1(TIMES),
DIVIDE1(DIVIDE);
private final Operation operation;
OperationType(Operation operation) {
this.operation = operation;
}
enum Operation {
PLUS{
@Override
double apply(double x, double y) {
return x + y;
}
},
MINUS{
@Override
double apply(double x, double y) {
return x - y;
}
},
TIMES{
@Override
double apply(double x, double y) {
return x * y;
}
},
DIVIDE{
@Override
double apply(double x, double y) {
return x / y;
}
};
abstract double apply(double x, double y);
}
}
전략 열거 타입 패턴도 좋지만 상수별 동작을 혼합해 넣을 때는 switch 문이 좋은 선택이 될 수 있다.
정리
- 필요한 원소를 컴파일타임에 다알 수 있는 상수 집합이라면 항상 열거 타입을 사용하자
- 열거 타입에 정의된 상수 개수가 영원히 공정 불변일 필요는 없다.
- 하나의 메서드가 상수별로 다르게 동작해야 할 때 상수별 메서드를 구현하자.
- 상수 일부가 같은 동작을 공유한다면 전략 열거 타입 패턴을 사용하자.
'Book > Effective Java' 카테고리의 다른 글
[Item 33] 타입 안전 이종 컨테이너를 고려하라 (0) | 2022.09.02 |
---|---|
[Item 32] 제네릭과 가변인수를 함께 쓸 때는 신중하라 (0) | 2022.08.31 |
[Item 31] 한정적 와일드카드를 사용해 API 유연성을 높이라 (0) | 2022.08.31 |
[Item 30] 이왕이면 제네릭 메서드로 만들라 (0) | 2022.08.30 |
[Item 29] 이왕이면 제네릭 타입으로 만들라 (0) | 2022.08.18 |
[Item 28] 배열보다는 리스트를 사용하라 (0) | 2022.08.18 |
[Item 27] 비검사 경고를 제거하라 (0) | 2022.08.06 |
[Item 26] 로 타입(Raw Type)은 사용하지 말라 (0) | 2022.08.05 |