[Java] Enum을 도대체 왜 쓰는 것일까?
필자는 처음에 Enum의 필요성을 잘 못느꼈다. 그냥 상수들을 모아놓은 것 뿐인데 왜 사용하는 건지 그 이유를 모르겠었더랬다. 하지만 이 포스팅을 써서 Enum을 사용하는 이유를 한번 정리해보겠다.
🧐 Enum이란 무엇인가?
오라클 공식 문서에 따르면, enum은 “미리 정의된 상수들의 특별한 집합”이다. 영어 단어 ‘enumeration’의 줄임말로, 말 그대로 상수들을 열거한 것이다.
Enum의 특징:
- 외부에서 추가 객체 생성 없이 사용 가능
- 불변(immutable)이며, 암시적으로
public static final - 대문자로 작성하는 것이 관례
기본적인 Enum 작성 예시:
1
2
3
4
public enum Day {
SUNDAY, MONDAY, TUESDAY, WEDNESDAY,
THURSDAY, FRIDAY, SATURDAY;
}
열거 시에는 쉼표(,)로 구분하고, 마지막에는 세미콜론(;)으로 끝낸다.
Enum의 고급 기능: 생성자와 메서드
Enum은 단순히 상수의 나열을 넘어 클래스처럼 생성자와 메서드를 가질 수 있다. 이를 통해 더 복잡한 로직을 구현할 수 있다.
행성의 질량과 반지름을 이용한 Enum 예시:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public enum Planet {
MERCURY(3.303e+23, 2.4397e6),
VENUS (4.869e+24, 6.0518e6),
EARTH (5.976e+24, 6.37814e6),
MARS (6.421e+23, 3.3972e6),
JUPITER(1.9e+27, 7.1492e7),
SATURN (5.688e+26, 6.0268e7),
URANUS (8.686e+25, 2.5559e7),
NEPTUNE(1.024e+26, 2.4746e7);
private final double mass; // 킬로그램 단위
private final double radius; // 미터 단위
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
}
private double mass() { return mass; }
private double radius() { return radius; }
// 만유인력 상수 (m3 kg-1 s-2)
public static final double G = 6.67300E-11;
double surfaceGravity() {
return G * mass / (radius * radius);
}
double surfaceWeight(double otherMass) {
return otherMass * surfaceGravity();
}
public static void main(String[] args) {
if (args.length != 1) {
System.err.println("Usage: java Planet <earth_weight>");
System.exit(-1);
}
double earthWeight = Double.parseDouble(args[0]);
double mass = earthWeight/EARTH.surfaceGravity();
for (Planet p : Planet.values())
System.out.printf("Your weight on %s is %f%n",
p, p.surfaceWeight(mass));
}
}
이 예제에서 각 행성은 질량과 반지름 값을 가지며, 이를 이용해 표면 중력과 무게를 계산할 수 있다. values() 메서드를 사용하면 모든 enum 상수를 배열로 얻을 수 있어 순회가 가능하다.
🌟 Enum을 사용해야 하는 이유
우아한형제들 기술 블로그에서 언급된 enum 사용의 주요 이점은 다음과 같다:
- IDE의 강력한 지원
- 자동 완성, 오타 검증, 리팩토링 등
- 허용 가능한 값들의 제한
- 리팩토링 시 변경 범위 최소화
- 내용 추가 시 Enum 코드 외 수정 불필요
이러한 이점들을 실제 사례를 통해 살펴보겠다.
🔗 데이터 간 연관관계 표현
정산 시스템에서 다양한 테이블 간 데이터 변환이 필요한 경우를 생각해보자.
기존 방식:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class LegacyCase {
public String toTable1Value(String originValue) {
if("Y".equals(originValue)) {
return "1";
} else {
return "0";
}
}
public String toTable2Value(String originValue) {
if("Y".equals(originValue)) {
return "true";
} else {
return "false";
}
}
}
이 방식의 문제점:
- 메서드를 일일이 만들어야 하는 번거로움
- 동일한 의미의 값들(“Y”, “1”, true)을 일관되게 관리하기 어려움
Enum을 사용한 개선:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public enum TableStatus {
Y("1", true),
N("0", false);
private String table1Value;
private boolean table2Value;
TableStatus(String table1Value, boolean table2Value) {
this.table1Value = table1Value;
this.table2Value = table2Value;
}
public String getTable1Value() {
return table1Value;
}
public boolean isTable2Value() {
return table2Value;
}
}
사용 예:
1
2
String table1Value = TableStatus.Y.getTable1Value();
boolean table2Value = TableStatus.Y.isTable2Value();
이렇게 하면 관련된 값들을 한 곳에서 관리할 수 있어 일관성 유지와 유지보수가 쉬워진다.
🔄 상태와 행위의 통합 관리
간단한 사칙연산 계산기를 예로 들어보겠다.
기존 방식:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class LegacyOperation {
private String operator;
public LegacyOperation(String operator) {
this.operator = operator;
}
public double apply(double a, double b) {
switch (operator) {
case "+":
return a + b;
case "-":
return a - b;
case "*":
return a * b;
case "/":
return a / b;
}
throw new AssertionError("Not handled operation : " + operator);
}
}
Enum을 사용한 개선 (Java 8 이상):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public enum Operation {
PLUS((a, b) -> a + b),
MINUS((a, b) -> a - b),
TIMES((a, b) -> a * b),
DIVIDE((a, b) -> a / b);
private final BiFunction<Double, Double, Double> operation;
Operation(BiFunction<Double, Double, Double> operation) {
this.operation = operation;
}
public Double compute(Double x, Double y) {
return operation.apply(x, y);
}
}
이 방식의 장점:
- 각 연산자와 그에 대한 동작이 하나의 enum 상수로 캡슐화됨
- 람다 표현식을 사용해 간결하고 명확한 코드 작성 가능
- 새로운 연산 추가 시 enum에만 추가하면 됨
사용 예:
1
double result = Operation.PLUS.compute(5.0, 3.0);
📊 데이터 그룹 관리
결제 시스템에서 다양한 결제 수단을 관리해야 하는 경우를 생각해보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public enum PayGroup {
CASH("현금", Arrays.asList("ACCOUNT_TRANSFER", "REMITTANCE", "ON_SITE_PAYMENT")),
CARD("카드", Arrays.asList("CREDIT_CARD", "KAKAO_PAY", "PAYCO")),
ETC("기타", Arrays.asList("POINT", "COUPON")),
EMPTY("없음", Collections.emptyList());
private String title;
private List<String> payList;
PayGroup(String title, List<String> payList) {
this.title = title;
this.payList = payList;
}
public static PayGroup findByPayCode(String code){
return Arrays.stream(PayGroup.values())
.filter(payGroup -> payGroup.hasPayCode(code))
.findAny()
.orElse(EMPTY);
}
public boolean hasPayCode(String code){
return payList.stream()
.anyMatch(pay -> pay.equals(code));
}
}
이 방식의 장점:
- 결제 수단을 그룹화하여 관리 용이
- 새로운 결제 수단 추가 시 해당 그룹의 enum 상수만 수정하면 됨
findByPayCode메서드로 코드에 해당하는 그룹을 쉽게 찾을 수 있음
🎯 결론
Enum은 단순한 상수 그룹화 이상의 기능을 제공한다. 타입 안정성(type-safety)을 보장하여 컴파일 시점에서 오류를 잡을 수 있고, 코드의 가독성과 유지보수성을 크게 향상시킨다.
Enum을 적절히 활용하면:
- 관련된 상수들을 논리적으로 그룹화할 수 있다.
- 특정 데이터 타입에 대한 가능한 모든 값을 명시적으로 정의할 수 있다.
- 상태와 행위를 하나의 객체에 캡슐화할 수 있다.
- 코드의 자체 문서화 효과를 높일 수 있다.
참고
Leave a comment