Enum: 열거 타입
Enum은 일정 개수의 상수 값을 정의한 다음 그 외의 값을 허용하지 않는 타입이다. 연관된 상수를 묶어 표현한다.
Enum이 나오기 전까지는 아래 처럼 정수 상수를 한 묶음 선언해서 사용했다.
정수 열거 패턴: Enum나오기 전 쓰던 방법
public static final int APPLE_FUJI = 0;
public static final int APPLE_PIPPIN = 1;
public static final int APPLE_GRANNY_SMITH = 2;
public static final int ORANGE_NAVEL = 0;
public static final int ORANGE_TEMPLE = 1;
public static final int ORANGE_BLOOD = 2;
- 타입 안전을 보장할 수 없으며, 표현력이 좋지 않다.
- 상수 값이 같기 때문에 ORANGE 대신 APPLE를 사용해도 컴파일러는 경고 메시지를 출력하지 않는다.
- 접두어를 사용하여 이름 충돌을 방지하지만 관리가 어렵다.
- 정수 열거 그룹에 속한 상수를 순회하는 방법도 마땅하지 않다.
Enum
public enum Apple {FUJI, PIPPIN, GRANNY_SMITH}
public enum Orange {NAVEL, TEMPLE, BLOOD}
기존의 열거 패턴의 대안인 열거 타입(Enum) 이다. 위는 열거 타입의 가장 단순한 형태이다.
Java의 열거 타입은 C, C++ 같은 다른 열거 타입 언어보다 강력함을 지니는데 그 이유는 바로
Java의 열거 타입은 완전한 형태의 클래스이기 때문이다.
- 열거 타입 자체는 클래스이다.
- 상수는 public static final 필드로 상수 하나당 자신의 인스턴스 하나씩만 만든다.
- 열거 타입은 밖에서 접근할 수 있는 생성자를 제공하지 않는다.
→ Enum 인스턴스들은 딱 하나씩만 존재함이 보장된다.
Enum에 메서드나 필드를 추가하기
Enum 클래스는 단순한 상수 모음으로 쓰지만 Java에서의 Enum은 메서드나 필드를 추가함으로 고차원의 추상 개념 하나를 표현할 수 있다. Java에서 Enum은 클래스이기 때문이다!!
public enum Planet {
//질량, 반지름
MERCURY(3.302e+23, 2.439e6),
VENUS (4.869e+24, 6.052e6),
EARTH (5.975e+24, 6.378e6),
MARS (6.419e+23, 3.393e6),
JUPITER(1.899e+27, 7.149e7),
SATURN (5.685e+26, 6.027e7),
URANUS (8.683e+25, 2.556e7),
NEPTUNE(1.024e+26, 2.477e7);
private final double mass; // 질량(단위: 킬로그램)
private final double radius; // 반지름(단위: 미터)
private final double surfaceGravity; // 표면중력(단위: m / s^2)
// 중력상수(단위: m^3 / kg s^2)
private static final double G = 6.67300E-11;
// 생성자
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
surfaceGravity = G * mass / (radius * radius);
}
public double mass() { return mass; }
public double radius() { return radius; }
public double surfaceGravity() { return surfaceGravity; }
public double surfaceWeight(double mass) {
return mass * surfaceGravity; // F = ma
}
}
- 각 행성은 질량과 반지름을 갖고 있다.
이때 Enum은 근본적으로 불변이기 때문에 당연히 모든 필드는 final이어야 한다. - 생성자에서 데이터를 받아 인스턴스 필드에 저장하게 된다.
- 메서드를 추가하여 추가적인 정보를 받을 수도 있다.
Enum 상수별 메서드 구현
public enum 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;
Operation(String symbol) { this.symbol = symbol; }
@Override
public String toString() { return symbol; }
public abstract double apply(double x, double y);
}
열거타입에 추상메서드를 선언하고, 각 상수별 클래스 몸체에 상수에 맞게 재정의하는 방법이 있다.
Enum 안에 정의된 상수 순회하기: values()
Enum 클래스는 안에 정의된 상수들의 값을 배열에 담아 반환할 수 있다.
public class WeightTable {
public static void main(String[] args) {
double earthWeight = Double.parseDouble(args[0]);
double mass = earthWeight / Planet.EARTH.surfaceGravity();
for (Planet p : Planet.values())
System.out.printf("%s에서의 무게는 %f이다.\n",
p, p.surfaceWeight(mass));
}
}
Enum에서 ordinary 메서드 대신 인스턴스 필드를 사용하자
대부분의 열거 타입 상수는 하나의 정수값에 대응된다.
ordinal 메서드: 해당 상수가 열거 타입에서 몇 번째 위치인지를 반환한다.
ordinal 메서드는 EnumSet, EnumMap과 같이 결거 타입 기반의 범용 자료구조에 쓸 목적으로 설계되었기 때문에 웬만한 프로그래머들은 이 메서드를 이용할 일이 없다. 앞서 설명한 이유가 아닌 목적으로 사용할 경우 아래와 같은 단점이 있다.
- 유지보수가 어렵다.
- 상수 선언 순서가 바뀌면 numberOfMusicians가 오동작한다.
- 이미 사용 중인 상수와 같은 값을 사용할 수 없다.
- 값을 중간에 비울 수가 없다. 사용하기 위해서는 dummy 상수를 추가해주어야 한다
해결
열거 타입 상수에 연결된 값은 ordinal 메서드로 얻지 말고 인스턴스 필드에 저장하자
ordinal 인덱싱 대신 EnumMap 사용
ordinal 메서드를 배열 인덱스로 사용
Set<Plant>[] plant ByLisfeCycle = (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.lifeCycler.ordinal()].add(p);
}
//결과 출력
for(int i = 0; i < plantsByLifeCycle.length; i++) {
System.out.println("%s: %s%n",
Plant.LifeCycle.values()[i], plantsByLifeCycle[i] );
}
- 정수 값을 정확히 사용해야 한다. 잘못 사용하면 ArrayIndexOutOfBoundsException을 던진다.
EnumMap을 사용해 데이터와 열거 타입을 매핑
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);
'Reading Book > 이펙티브 자바' 카테고리의 다른 글
[이펙티브 자바] 자바 프로그래밍 원칙 (0) | 2022.12.12 |
---|---|
[이펙티브 자바] 메서드 설계 주의점 (0) | 2022.12.12 |
[이펙티브 자바] 클래스와 인터페이스를 유연하게 만드는 방법 (0) | 2022.12.12 |
[이펙티브 자바] Object 메소드 관련 규약 + Comparable (0) | 2022.12.12 |
[이펙티브 자바] try-finally 보다 try-with-resources 사용하자 (0) | 2022.12.12 |