우선, 글을 작성하기 전 이 글의 모든 내용은 김영한님의 JAVA 강의를 바탕으로 함을 알립니다.
열거형(Enum Type)은 Java에서 고정된 상수 값들의 집합을 정의할 때 사용하는 데이터 타입이다. 열거형을 사용하면 코드의 가독성, 안정성, 유지보수성이 높아진다고 나와있다. 그렇다면 열거형을 사용하지 않았을 때의 문제 즉, 열거형의 필요성을 알아보기위해 열거형이 만들어진 근본적인 이유를 알아야한다.
열거형의 경우 주로 값의 범위가 고정적이고 제한적일 때 사용한다. 이를 열거형이 아닌 문자열을 사용한 예제를 통해 문제점을 살펴보자.
회원 등급과 가격을 입력하면 할인 금액을 계산해주는 예제
/**
* 고객은 3등급으로 나누고, 상품 구매시 등급별로 할인을 적용한다. 할인시 소수점 이하는 버린다.
* BASIC 10% 할인
* GOLD 20% 할인
* DIAMOND 30% 할인
*/
public class DiscountService {
public int discount(String grade, int price) {
int discountPercent = 0;
if (grade.equals("BASIC")) {
discountPercent = 10;
} else if (grade.equals("GOLD")) {
discountPercent = 20;
} else if (grade.equals("DIAMOND")) {
discountPercent = 30;
} else {
System.out.println(grade + ": 할인X");
}
return price * discountPercent / 100;
}
}
public class StringGradeEx0_1 {
public static void main(String[] args) {
int price = 10000;
DiscountService discountService = new DiscountService();
int basic = discountService.discount("BASIC", price);
int gold = discountService.discount("GOLD", price);
int diamond = discountService.discount("DIAMOND", price);
System.out.println("BASIC 등급의 할인 금액: " + basic);
System.out.println("GOLD 등급의 할인 금액: " + gold);
System.out.println("DIAMOND 등급의 할인 금액: " + diamond);
/**
* 출력 결과
* BASIC 등급의 할인 금액: 1000
* GOLD 등급의 할인 금액: 2000
* DIAMOND 등급의 할인 금액: 3000
*/
}
}
위 코드는 고객의 등급에 따라 할인 비율을 책정하여 할인된 금액을 반환하는 예제로, 고객의 등급(grade)을 String타입으로 생성하였다. 출력의 결과만 보면 정상적으로 프로그램이 실행되고 문제가 없어보이지만 타입 안정성과 데이터 일관성측면에서 문제점이 존재한다.
public class StringGradeEx0_2 {
public static void main(String[] args) {
int price = 10000;
DiscountService discountService = new DiscountService();
// 존재하지 않는 등급
int vip = discountService.discount("VIP", price);
System.out.println("VIP 등급의 할인 금액: " + vip);
// 오타
int diamondd = discountService.discount("DIAMONDD", price);
System.out.println("DIAMONDD 등급의 할인 금액: " + diamondd);
// 소문자 입력
int gold = discountService.discount("gold", price);
System.out.println("gold 등급의 할인 금액: " + gold);
/**
* VIP: 할인X
* VIP 등급의 할인 금액: 0
* DIAMONDD: 할인X
* DIAMONDD 등급의 할인 금액: 0
* gold: 할인X
* gold 등급의 할인 금액: 0
*/
}
}
값의 제한 부족
BASIC,DIAMOND,GOLD에 따른 고객의 할인 비율 계산 및 할인 된 가격 제공이다. 즉, 3개의 등급을 제외한 다른 값은 할인 적용이 불가능해야한다.VIP), 오타(DIAMONDD), 소문자(gold)에 대해서는 제약이 필요하지만 제약이 발생하지 않는다컴파일 시 에러 감지 불가
제약이 발생하지 않는 이유는 String타입에 맞게 코드가 작성되었기 때문이다. 즉, 컴파일 과정에서 에러는 발생하지 않기에 개발자가 런타임에서 디버깅을 직접하여 문제점을 파악해야한다. 위의 문제를 해결하려면 등급(grade)을 특정 값으로 제한해야 한다. 즉, 등급으로 반드시 BASIC,GOLD,DIAMOND만 적용되도록 해야한다.
문자열 상수를 활용해보자.
public class StringGrade {
public static final String BASIC = "BASIC";
public static final String GOLD = "GOLD";
public static final String DIAMOND = "DIAMOND";
}
public class DiscountService {
public int discount(String grade, int price) {
int discountPercent = 0;
if (grade.equals(StringGrade.BASIC)) {
discountPercent = 10;
} else if (grade.equals(StringGrade.GOLD)) {
discountPercent = 20;
} else if (grade.equals(StringGrade.DIAMOND)) {
discountPercent = 30;
} else {
System.out.println(grade + ": 할인X");
}
//10000 * 20
return price * discountPercent / 100;
}
}
public class StringGradeEx1_1 {
public static void main(String[] args) {
int price = 10000;
DiscountService discountService = new DiscountService();
int basic = discountService.discount(StringGrade.BASIC , price);
int gold = discountService.discount(StringGrade.GOLD, price);
int diamond = discountService.discount(StringGrade.DIAMOND, price);
System.out.println("BASIC 등급의 할인 금액: " + basic);
System.out.println("GOLD 등급의 할인 금액: " + gold);
System.out.println("DIAMOND 등급의 할인 금액: " + diamond);
}
}
문자열 상수를 사용하면 가독성이 올라가고, 상수형태의 등급을 사용하기 때문에 잘못된 문자열 상수값을 입력할 시 컴파일 에러가 발생하여 오류를 쉽고 빠르게 찾을 수 있다.
하지만, String타입으로 인해 다른 문자열이 입력되었을 때는 여전히 컴파일 과정에서 오류를 찾을 수 없다는 문제점에 직면한다. 또한 만약 코드가 길어지고 복잡하여 여러 개발자가 파트를 나누어 개발을 한다고 가정하자. A라는 개발자가 문자열 상수를 사용하였다면 이를 B라는 개발자는 직접 건네 듣거나, 주석을 달지 않는 이상 알수없다. 즉, 매우 비효율적인 개발 프로세스가 된다는 것이다.
고정된 범위의 값을 표현할 때 String타입을 사용할 시,
값의 제한 부족로 인한 타입 안정성 약화 컴파일 시 오류 감지 불가로인한 디버깅 어려움이를 해결하기 위해 고정된 범위를 가독성 높게 표현하며 해당 범위의 이외의 값에 대해 강력한 제약을 제공해야한다
public class ClassGrade {
public static final ClassGrade BASIC = new ClassGrade(); // x001
public static final ClassGrade GOLD = new ClassGrade(); // x002
public static final ClassGrade DIAMOND = new ClassGrade(); // x003
//private 생성자 추가
//private 생성자 --> ClassGrade 외부에서 객체를 생성하여 클래스로 접근하는 것을 방지
private ClassGrade() {}
}
위의 코드는 ENUM과 거의 같은 코드이다. 두 가지 핵심에 대해 살펴보자.
[1]
public static final ClassGrade BASIC = new ClassGrade(); // x001
public static final ClassGrade GOLD = new ClassGrade(); // x002
public static final ClassGrade DIAMOND = new ClassGrade(); // x003
public을 통해 외부에서 접근이 가능하되, final을 통해 값을 고정시켜 값 변경은 제한메모리 참조값을 상수화하여 사용 [2]
private ClassGrade() {}
private 생성자를 통해 클래스 외부에서 객체를 생성하는 것을 방지하여 클래스 내부에서만 객체를 생성하도록 설정상수를 통해 고정된 범위를 표현 가능 
위에서 만든 타입 안전 열거형 패턴을 기반으로 할인율을 적용하는 로직을 살펴보자.
public class DiscountService {
public int discount(ClassGrade classGrade, int price) {
int discountPercent = 0;
if (classGrade == ClassGrade.BASIC) { // classGrade == x001
discountPercent = 10;
} else if (classGrade == ClassGrade.GOLD) {
discountPercent = 20;
} else if (classGrade == ClassGrade.DIAMOND) {
discountPercent = 30;
} else {
System.out.println("할인X");
}
return price * discountPercent / 100;
}
}
public class StringGradeEx2_1 {
public static void main(String[] args) {
int price = 10000;
DiscountService discountService = new DiscountService();
int basic = discountService.discount(ClassGrade.BASIC,price); // == discountService.discount(x001, price)
int gold = discountService.discount(ClassGrade.GOLD,price);
int diamond = discountService.discount(ClassGrade.DIAMOND,price);
// 불가능한 경우 -> 컴파일 에러 발생
//int errorcase1 = discountService.discount(2,price);
//int errorcase2 = discountService.discount(new ClassGrade(),price);
//int errorcase3 = discountService.discount(ClassGrade.VIP,price);
System.out.println("BASIC 등급의 할인 금액: " + basic);
System.out.println("GOLD 등급의 할인 금액: " + gold);
System.out.println("DIAMOND 등급의 할인 금액: " + diamond);
}
DiscountService 의 discount메서드를 보면 매개변수로 ClassGrade타입의 객체를 받아 BASIC,GOLD,DIAMOND와 동일성비교를 통해 할인율을 적용한다. 이때 매개변수인 ClassGrade는 외부에서 객체 생성이 불가능함을 확인하였고 main메서드의 불가능한 경우를 살펴보자ClassGrade에서 사전에 정의한 객체를 제외하고는 전부 매개변수로 사용할 수 없고 이로인해 컴파일 에러가 발생함을 알 수 있다. 타입 안전 열거형 패턴(Type-Safe Enum Pattern)의 장점
제한된 인스턴스 생성
타입 안정성
ClassGrade의 경우 오로지 BASIC,GOLD,DIAMOND만 사용 가능 단점
private 생성자 추가에 유의해야함이 단점을 보완하고 편리하게 사용할 수 있도록 java에서는 열거형(Enum Type)을 제공한다.
다음 포스팅에서는 열거형(Enum Type)과 열거형 리팩토링에 대해 추가 포스팅을 진행할 예정이다.