enum 이란?
Enum이란 Enumeration의 앞 글자로 열거라는 의미를 갖는다.
관련이 있는 상수들의 집합입니다.
자바에서는 final로 String과 같은 문자열이나 숫자들을 나타내는 기본 자료형의 값을 고정할 수 있다.
이렇게 고정된 값을 상수라고 한다.
어떤 클래스가 상수만으로 작성되어 있으면 반드시 class로 선언할 필요는 없습니다.
이럴 때 class로 선언된 부분에 enum이라고 선언하면 이 객체는 상수의 집합이다. 라는 것을 명시적으로 나타낼 수 있다.
enum의 기본구조
public enum [클래스명] {
상수1, 상수2;
}
enum은 상수들의 집합이며, 각각의 상수는 ,로 구분한다.
마지막 상수 뒤에는 세미콜론(;)이 와야한다.
enum 사용 예시 1
public enum Enum1 {
MERCURY, VENUS, EARTH;
}
public class EnumEx {
public static void main(String[] args) {
for (Enum1 fruit : Enum1.values()) {
System.out.println(fruit);
}
}
}
// MERCURY
// VENUS
// EARTH
기본 사용예시는 위와 같다.
values를 이용하여 enum 클래스에 속해 있는 상수들을 꺼내서 살펴볼 수 있다.
조금 더 살펴보자.
데이터를 갖는 열거 타입
각각의 열거 타입은 데이터(속성)를 연결지을 수 있다.
아래 예시를 살펴보자.
public enum Planet {
MERCURY(3, 2), VENUS(4, 6), EARTH(5, 6);
private final double mass; // 질량(단위: 킬로그램)
private final double radius; // 반지름(단위: 미터)
// 생성자
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
}
}
열거 타입 상수 각각을 특정 데이터와 연결지으려면 생성자에서 데이터를 받아 인스턴스 필드에 저장하면 된다.
MERCURY는 3과 2의 데이터를 가지고 있고, 생성자에 mass, radius 라는 데이터를 받고 있다.
MERCURY의 mass는 3, radius는 2가 된다.
이러한 방식을 이용하여 상수와 데이터를 연결시켜줄 수 있다.
그러나 한가지 문제가 있다.
mass, radius의 접근 제한자는 private이다.
(열거 타입은 근본적으로 불변이라 모든 필드는 final이어야 한다. 피드를 public으로 선언해도 되지만, private으로 두고 아래와 같이 public 접근자 메서드를 두는 것이 좋은 방법이다.)
mass와 radius 변수를 사용하려면 새로운 메소드가 필요하다.
메소드를 추가해주자.
데이터와 메소드를 갖는 열거 타입
public enum Planet {
MERCURY(3, 2), VENUS(4, 6), EARTH(5, 6);
private final double mass; // 질량(단위: 킬로그램)
private final double radius; // 반지름(단위: 미터)
// 생성자
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
}
public double mass() {
return mass;
}
public double radius() {
return radius;
}
}
public class EnumEx2 {
public static void main(String[] args) {
for (Planet planet : Planet.values()) {
System.out.println(planet);
System.out.println(planet.mass());
System.out.println(planet.radius());
System.out.println();
}
}
}
// MERCURY
// 3.0
// 2.0
// VENUS
// 4.0
// 6.0
// EARTH
// 5.0
// 6.0
조금 그럴듯 한가??
다른 예제도 살펴보자.
상수별 메소드 구현을 활용한 열거 타입.
public enum Operation1 {
PLUS, MINUS, TIMES, DIVIDE;
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 DIVIDE: return x/y;
}
throw new AssertionError("알 수 없는 연산: " + this);
}
}
Operation1 을 살펴보자.
PLUS, MINUS, TIMES, DIVIDE 각각의 기능을 switch case를 이용하여 구현하였다.
작동은 잘하지만 한가지 문제가 있다.
새로운 상수를 추가하면, 거기에 맞는 새로운 case를 만들어 줘야한다.
그러나 개발자가 까먹고 재정의하지 않았다면 "알 수 없는 연산" 이라는 에러를 내고 죽게된다.
더 좋은 방법은 없을까?
아래에서 살펴보자.
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; }};
public abstract double apply(double x, double y);
}
각각의 상수에 알맞는 apply 메소드를 상수 바로 옆에 선언하였다.
이렇게 되면 개발자가 새로운 상수를 추가할 때 새로운 apply 메소드도 재정의해야 한다는 사실을 깜빡하기는 어려울 것이다.
추가적으로 apply가 추상메서드로 되어있기 때문에, 개발자가 apply 메소드를 재정의 하지 않았더라도 컴파일 오류로 알려줄 것이다.
앞서 배운 상수에 데이터(속성)을 추가하고, toString 메소드를 오버라이딩 해보자.
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;
}
public abstract double apply(double x, double y);
@Override
public String toString() {
return symbol;
}
}
Operation1Main 클래스를 통해 실행 결과를 살펴보자.
public class Operation1Main {
public static void main(String[] args) {
for (Operation operation : Operation.values()) {
System.out.printf("%s %s %s = %s%n", "4", operation, "6", operation.apply(4, 6));
}
}
}
// 4 + 6 = 10.0
// 4 - 6 = -2.0
// 4 * 6 = 24.0
// 4 / 6 = 0.6666666666666666
완벽하다!
전략 열거 타입
마지막으로 살펴볼 것은 전략 열거 타입이다.
public enum PayrollDay {
MONDAY, TUESDAY, WEDNESDAY, THURTHDAY, FRIDAY,
SATURDAY, SUNDAY;
private static final int MINS_PER_SHIFT = 8 * 60;
int pay(int minutesWorked, int payRate) {
int basePay = minutesWorked * payRate;
int overtimePay;
switch(this) {
case SATURDAY:
case SUNDAY:
overtimePay = basePay / 2;
break;
default:
overtimePay = minutesWorked <= MINS_PER_SHIFT ?
0 : (minutesWorked - MINS_PER_SHIFT) * payRate / 2;
}
return basePay + overtimePay;
}
}
마지막 코드는 급여에 관한 것이다.
코드는 주말에 출근한 경우 기본급의 반틈만큼 더 받고
평일에는 야근을 했을 경우에만 시급의 반틈만큼 추가금을 받게 된다는 코드이다.
간결한 코드이지만 관리 관점에서는 매우 위험한 코드이다.
vacation이라는 새로운 상수가 들어왔을 때는, 주말에 출근하였지만 평일 수당을 받을 수 있는 것이다.
어떻게 수정하면 좋을까?
이럴 때 전략 열거 타입을 사용하면 된다.