-
Notifications
You must be signed in to change notification settings - Fork 0
Description
section: (6장 아이템 34)
🍵 서론
열거 타입이란 상수의 값을 미리 정의한 다음, 그 외의 값은 허용하지 않는 타입으로 “월요일, 화요일 등” 제한된 값의 집합을 표현할 때 등 다양한 형태로 사용된다.
- 제한된 값의 집합을 표현
- 가독성과 유지보수성 향상
- 타입 안전성 제공
- switch 문과 호환
- API 설계에 활용
자바에서는 열거타입을 지원하기 전에 정수 상수를 잔뜩 선언하여 이를 사용하곤 했지만, 다양한 단점이 있었고 이를 배경으로 열거 타입이 등장하게 되었다. 지금부터 그 단점이 뭔지 열거 타입이 단점을 어떻게 극복했는지, 열거 타입의 특징을 알아보자.
🌒 본론
public class Week {
public static final int MON = 0;
public static final int TUE = 1;
public static final int WED = 2;
public static final int THU = 3;
public static final int FRI = 4;
public static final int SAT = 5;
public static final int SUN = 6;
public static final int DAY_FIRST = 1;
public static final int DAY_SECOND = 2;
}자바에서는 enum 타입 등장 전에 정수 열거 패턴(int enum pattern)으로 상수를 표현했다. 위 패턴은 다양한 단점이 있다.
- TUE와 DAY_FIRST는 동등 연산자로 비교했을 때 경고 메시지를 출력하지 않아 타입 안전성이 깨진다.
- 별도의 namespace를 지원하지 않기 때문에 이름 충돌을 방지하기 위해 접두사를 집어 넣어야 한다.
- 단순히 상수를 나열한 개념이므로 컴파일하면 그 값이 클라이언트 파일에 그대로 남는다. 상수의 값을 바꾸면 반드시 클라이언트는 재컴파일해야 한다.
- 값을 출력하거나 디버거로 살펴보면 단지 숫자로 보인다. 즉, 문자열로 출력하기가 까다롭다.
단점이 수도 없이 많은 이 패턴은 열거 타입의 등장과 함께 사라졌다.
열거 타입은 “클래스”
자바라는 언어의 특징, “클래스”는 열거 타입의 강점을 더욱 돋보이게 만든다.
열거 타입 자체는 클래스이고 상수 하나당 각각 자신의 인스턴스를 만들어 public static final 필드로 공개한다. 밖에서 클래스로 접근 가능한 생성자가 없으므로 사실상 final이고 클라이언트는 인스턴스를 직접 확장하거나 생성할 수 없으므로 열거 타입은 싱글턴 형태로 완벽하게 통제된다.
”상수”라는 성격은 클래스의 특징으로 간단하게 지켜질 수 있게 된다.
타입 안전성 제공
public enum Color {
RED,
GREEN,
BLUE;
}열거 타입이 사전에 정의된 상수 집합으로 제한되기 때문에 컴파일 타임에서 타입 안전성을 제공할 수 있다. 열거 타입 이외에는 값을 허용하지 않음을 의미하게 된다. 이로써 코드에서 발생할 수 있는 일부 에러를 컴파일 타임에서 방지한다.
public void printColor(Color color) {
System.out.println("Selected color: " + color);
}
...
// 컴파일 오류: incompatible types
printColor("InvalidColor");별도의 namespace 제공
public enum Ssartel {
HONG,
CHOI,
LEE;
}
public enum Baksa {
HONG;
}싸르텔과 박사 열거 타입은 서로 독립적인 namespace를 가지고 있기 때문에 HONG은 각각 다른 상수를 나타낸다.
즉, 공개되는 것은 필드의 이름 뿐인 것이고, 클라이언트는 상수 값이 뭔지 알 수 없다.
이렇게 정수 열거 패턴의 단점은 가볍게 보완하는 enum은 여기서 끝나지 않는 장점을 보여준다.
별도 메서드나 필드를 추가, 인터페이스 구현 가능
Enum 타입은 Object의 메서드들을 구현해놓았고 Comparable, Serializable도 가능하도록 구현했으며 직렬화 형태에서도 어지간한 변형엔 문제를 일으키지 않도록 구현해놓았을 뿐 아니라 별도로 메서드나 필드를 추가하고 인터페이스를 구현할 수 있도록 설계하였다.
사실상 단순한 상수의 모음이 아니라 고차원의 추상 개념 하나를 표현하는 수단이 될 수도 있다.
와우!

각 상수와 연관된 데이터를 미리 해당 상수 자체와 연결시키고 싶다면 p.211에 코드처럼 열거 타입을 선언하고 생성자에서 데이터를 받아 인스턴스 필드에 저장하자.
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),
URAUS(8.683e+25,2.556e7),
NEPTUNE(1.024e+26,2.477e7);
// 임의의 필드
private final double mass; // 질량
private final double radius; // 반지름
private final double surfaceGravity; // 표면중력
// 중력상수
private static final double G = 6.67300E-11;
// 생성자
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
this.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;
}
}접근제한자에 유의하자.(item 15)
일반 클래스의 접근제한자가 가져야할 조건과 유사하다. 열거 타입을 선언한 클래스 혹은 패키지에서만 유효한 기능은 private 혹은 package-private(default)로 구현해야 한다.
Switch문이 약점이다. 상수별 메서드 구현으로 해결하자.
각 enum이 가지는 상수값에 따라 동작을 달리하는 메서드를 선언하고 싶다. enum은 switch문을 지원하니까 switch문으로 설계하면 문제없다고 생각하는 당신은…
public enum Operation {
PLUS,MINUS,TIMES,DIVDE;
public double apply(double x, double y) {
switch (this) {
case PLUS:
return x + y;
case MINUS:
return x - y;
case TIMES:
return x * y;
case DIVDE:
return x / y;
}
throw new AssertionError("알 수 없는 연산:" + this);
}
}위 코드를 상수별 메서드 구현으로 해결하자. 상수별로 특정 데이터와 연결 지을 수도 있다.
public enum Operation {
PLUS("+") {public double apply(double x, double y) {return x + y;}},
MINUS("-") {...},
...
private final String symbol;
Operation(String symbol) { this.symbol = symbol; }
public abstract double apply(double x, double y);
}전략 열거 타입 패턴
바로 위의 상수별 메서드 구현에는 열거 타입 상수끼리 코드를 공유하기 어렵다는 단점이 있다. 냅다 구현하려면 또 switch 문을 도입하여 위험천만한 코드가 탄생한다. 이 단점을 보완하기 위하여 전략 열거 타입 패턴을 사용해야 한다.
// 전략 열거 타입 패턴
public enum PayrollDay {
MONDAY(PayType.WEEKDAY),
TUESDAY(PayType.WEEKDAY),
WEDNESDAY(PayType.WEEKDAY),
THURSDAY(PayType.WEEKDAY),
FRIDAY(PayType.WEEKDAY),
SATURDAY(PayType.WEEKEND),
SUNDAY(PayType.WEEKEND);
private final PayType payType;
PayrollDay(PayType payType) {
this.payType = payType;
}
int pay(int minutesWorked, int payRate) {
return payType.pay(minutesWorked,payRate);
}
// private 중첩 열거 타입
private enum PayType {
WEEKDAY {
int overtimePay(int minutesWorked, int payRate) {
return minutesWorked <= MINS_PER_SHIFT ?
0 : (minutesWorked - MINS_PER_SHIFT) * payRate / 2;
}
},
WEEKEND {
int overtimePay(int minutesWorked, int payRate) {
return minutesWorked * payRate / 2;
}
};
abstract int overtimePay(int minutesWorked, int payRate);
private static final int MINS_PER_SHIFT = 8 * 60;
int pay(int minutesWorked, int payRate) {
int basePay = minutesWorked * payRate;
return basePay + overtimePay(minutesWorked,payRate);
}
}
}전략 열거 타입 패턴은 상수를 추가할 때 private 중첩 열거 타입으로 미리 선언된 전략을 선택하도록 하는 패턴이다.
열거 타입 패턴은 그래서 언제 사용해야 할까?
필요한 원소가 컴파일 타임에 다 알 수 있는 상수 집합이라면 열거 타입을 무조건 사용하자. 참고로 열거 타입에 정의된 상수 개수가 영원히 고정 불변일 필요는 없다. 열거 타입은 나중에 추가되어도 바이너리 수준에서 호환되도록 설계되었으므로 문제없다.
🍃 결론
사실 enum 내용이 이렇게까지 방대하며 할 이야기가 많을 거라고는 처음 알았다. enum을 쉽게 본 나는 반성하자.
추가로, 자바에서 열거 타입과 컴파일러의 관계성을 이야기했던 자바 성능 튜닝이야기의 “컴파일러는 어디까지 해줄까?”를 기억하는 조원이 있길 기도한다. 추억이 새록새록 피어나므로 한번씩 다시 봐주자.
