상수 객체의 모음집이며, 서로 관련있는 상수 값들을 모아서 사용할 때 유용하다.
상수의 역할을 하고, 각 상수가 객체이므로 프로퍼티나 메소드를 가질 수 있다.
상수의 역할을 하지만 클래스이기 때문에 생성자를 가짐(무조건 private)
내부 상수객체들은 각각 메모리에 하나만 존재하는 인스턴스이며, 만들어준 enum 클래스를 상속하고 있다. 따라서 enum 클래스 내부에 추상 메소드를 선언하면, 각 상수 객체에서 구현을 해줘야한다.
Enum을 활용해서 아래와 같은 상황에 조건문을 없애줄 수 있다.
public class Calculator {
public int calculate(String op, int a, int b) {
if ("+".equals(op)) {
return a + b;
} else if ("-".equals(op)) {
return a - b;
} else if ("*".equals(op)) {
return a * b;
} else if ("/".equals(op)) {
return a / b;
} else {
throw new IllegalArgumentException();
}
}
}
위의 코드에서 +
, -
, *
, /
는 연산자라는 서로 관련있는 상수로 볼 수 있다. 따라서 enum 클래스로 사용할 수 있다.
public enum Operator {
PLUS("+"),
MINUS("-"),
MULTIPLY("*"),
DIVIDE("/");
private final String operator;
Operator(String operator) {
this.operator = operator;
}
}
하지만 단지 enum 클래스로 연산자들을 묶는다고 해도 조건문을 없앨 수 있는 것은 아니다.
이때 정적 팩토리 메소드를 사용하고, enum 클래스에서 각 상수객체에게 행동을 하도록 시킨다면 조건문을 없앨 수 있다.
public int calculate(String op, int a, int b) {
Operator operator = Operator.from(op);
return operator.calculate(a, b);
}
정적 팩토리 메소드로 Operator 타입의 인스턴스를 가져오고, 계산이라는 행동을 Operator 객체에 요청한다. 계산은 연산자가 두 수를 가지고 하는 행위이므로, 계산이라는 행동을 Operator 객체에서 처리하게 하는 것은 타당해보인다.
public enum Operator {
PLUS("+") {
@Override
int calculate(int a, int b) {
return a + b;
}
},
MINUS("-") {
@Override
int calculate(int a, int b) {
return a - b;
}
},
MULTIPLY("*") {
@Override
int calculate(int a, int b) {
return a * b;
}
},
DIVIDE("/") {
@Override
int calculate(int a, int b) {
return a / b;
}
};
private final String operator;
Operator(String operator) {
this.operator = operator;
}
// 추상 메소드
abstract int calculate(int a, int b);
// 정적 팩토리 메소드
static Operator from(String value) {
for (Operator operator : values()) {
if (operator.operator.equals(value)) {
return operator;
}
}
throw new IllegalArgumentException();
}
}
처음에 살펴본 것 처럼 추상 메소드를 정의해주면, 각 상수 객체는 Operator를 상속하고 있기 때문에 추상 메소드를 구현해줘야한다. 각 상수가 객체이므로 메소드를 가질 수 있다는 점에 의해 각 상수 객체는 calculate 메소드를 override 하고 있다.
여기서 람다식을 사용해서 코드를 더 간결하게 만들 수도 있다.
public enum Operator {
PLUS("+", (a, b) -> a + b),
MINUS("-", (a, b) -> a - b),
MULTIPLY("*", (a, b) -> a * b),
DIVIDE("/", (a, b) -> a / b);
private final String operator;
private final BiFunction<Integer, Integer, Integer> calculate;
Operator(String operator, BiFunction<Integer, Integer, Integer> calculate) {
this.operator = operator;
this.calculate = calculate;
}
static Operator from(String value) {
for (Operator operator : values()) {
if (operator.operator.equals(value)) {
return operator;
}
}
throw new IllegalArgumentException();
}
public int calculate(int a, int b) {
return calculate.apply(a, b);
}
}
모든 연산자는 2개의 정수 인자를 받아서 나름대로 계산하고 하나의 정수 값을 반환하므로 BiFunction<T, U, R>
함수형 인터페이스를 사용할 수 있다.