Java Enum - 타입 안전의 열거형 패턴
앞서 작성된 글에서 타입으로 인한 문제점을 살펴보았고 개발자들이 이를 인지하여 자바 신규로 타입 안전 열거형 패턴이라는 기능이 추가되었다.
enum 단어는 enumeration 의 줄임말이다. 열거라는 뜻이 있으며 항목을 나열하는 것을 뜻한다. 이전 글에서 쿠폰 BRONZE, SILVER, GOLD 등급으로 나열한 것을 말한다.
타입 안전 열거형 패턴을 사용하면 나열된 항목만 사용할 수 있다. 나열한 항목이 아닌 것은 사용할 수 없다.
String 문자열로 기입할 수 없도록 하고 앞서 나열된 BRONZE, SILVER, GOLD 등급을 안전하게 사용 가능하다.
EnumGrade.class
public class EnumGrade {
public static final EnumGrade BRONZE = new EnumGrade();
public static final EnumGrade SILVER = new EnumGrade();
public static final EnumGrade GOLD = new EnumGrade();
}
열거형 코드는 이와 같이 구성한다.
메소드(클래스) 영역에 세가지가 만들어지고 인스턴스(힙) 영역은 각각 참조하는 형태로 될 것이다.
- EnumGrade 클래스를 생성하였다.
- 각 등급에는 별도의 인스턴스를 생성하고 대입한다.
- 각각의 등급으로 상수로 만들기 위해 static, final 키워드를 사용하였다.
- static 키워드는 메소드 영역에 선언한다.
- final 키워드는 인스턴스를 변경할 수 없도록 한다.
생성한 Enum 에서 각각의 클래스 정보를 조회해본다.
System.out.println("Class INFO = " + EnumGrade.BRONZE.getClass());
System.out.println("Class INFO = " + EnumGrade.SILVER.getClass());
System.out.println("Class INFO = " + EnumGrade.GOLD.getClass());
class EnumGrade
class EnumGrade
class EnumGrade
만든 세개는 EnumGrade 클래스로 생성했구나 알 수 있다.
System.out.println("Ref Info = " + EnumGrade.BRONZE);
System.out.println("Ref Info = " + EnumGrade.SILVER);
System.out.println("Ref Info = " + EnumGrade.GOLD);
Ref Info = EnumGrade@23fc625e
Ref Info = EnumGrade@3f99bd52
Ref Info = EnumGrade@4f023edb
EnumGrade 클래스의 인스턴스로 참조하기에 각각 다르게 조회된다.
쿠폰 등급별 할인 금액 출력하는 코드
각각의 등급별로 10,000원에서 쿠폰을 적용시 할인되는 금액을 표시한다.
DiscountService.class
public class DiscountService {
public int discount(EnumGrade grade, int price) {
int discountPercent = 0;
if (grade == EnumGrade.BRONZE) {
discountPercent = 10;
} else if (grade == EnumGrade.SILVER) {
discountPercent = 20;
} else if (grade == EnumGrade.GOLD) {
discountPercent = 30;
} else {
System.out.println("할인 없음");
}
return price * discountPercent / 100;
}
}
Main.class
public class Main {
public static void main(String[] args) throws Exception {
int price = 10000;
DiscountService disSrv = new DiscountService();
System.out.println(disSrv.discount(EnumGrade.BRONZE, price));
System.out.println(disSrv.discount(EnumGrade.SILVER, price));
System.out.println(disSrv.discount(EnumGrade.GOLD, price));
}
}
1000
2000
3000
실제로 적용되는 것을 볼 수 있다. discount 메소드에서 문자열로 기입할 수 없도록 막아 실수를 방지한다.
원리는 간단하다. 아래 코드를 살펴보자.
if (grade == EnumGrade.BRONZE) {
discountPercent = 10;
}
이 코드에서 인자로 들어온 grade 는 BRONZE 으로 들어오고 있을 때 참조 값은 "EnumGrade@23fc625e" 값이다.
그리고 EnumGrade.BRONZE 키워드는 "EnumGrade@23fc625e" 참조로 되어 있다.
if (EnumGrade@23fc625e == EnumGrade@23fc625e) {
discountPercent = 10;
}
조건문이 BRONZE와 일치하므로 할인이 적용된다.
새로운 문제점
discount 메소드는 "discount(EnumGrade grade, int price)" 파라미터로 되어있다. 개발자는 grade 파라미터를 보고 새로운 객체를 생성할 수 있다.
EnumGrade bronze = new EnumGrade();
이렇게 사용하여 기입한 경우 적용이 되지 않는다.
EnumGrade bronze = new EnumGrade();
System.out.println(disSrv.discount(bronze, price));
0
혼동의 여지를 충분히 발생할 수 있다.
이를 방지 하기로 EnumGrade 생성자를 막아야 한다.
EnumGrade.class
public class EnumGrade {
public static final EnumGrade BRONZE = new EnumGrade();
public static final EnumGrade SILVER = new EnumGrade();
public static final EnumGrade GOLD = new EnumGrade();
private EnumGrade() {}
}
정리
- private 생성자로 외부에서 EnumGrade 생성 제한
- EnumGrade 인스턴스를 생성하려면 EnumGrade 내부에서만 가능하다.
정의한 상수는 EnumGrade 클래스 내부에만 객체를 생성한다. - 외부로 EnumGrade 인스턴스를 사용 시 이미 정의한 상수만 사용해야 한다. 그렇지 않다면 컴파일 오류로 반환할 것이다.
private 생성자까지 완료하여 타입 안전 열거형 패턴을 완성할 수 있었다.
타입 안전 열거형 패턴 장점
- 타입 안정성 항상
- 정해진 객체 사용으로 잘못된 값을 입력하는 문제를 해결할 수 있다.
- 데이터 일관성
- 정해진 객체 사용으로 데이터 일관성 보장
실제 코드에서는?
- 인스턴스 생성 제한
- 클래스는 사전에 몇 개의 인스턴스를 생성하고 외부에서는 이미 생성한 인스턴스만 사용하도록 한다. 미리 정의된 값들만 사용하므로 보장한다.
- 타입 안전성
- 잘못된 값 할당이나 사용 시 컴파일 단계에서 장애를 방지한다. 특정 메소드가 특정 열거형 타입의 값을 요구하면 그 타입의 인스턴스만 전달한다.
- 메소드의 매개변수로 EnumGrade 떠올려 앞서 열거한 BRONZE, SILVER, GOLD 만 사용 가능하다.
앞서 EnumGrade의 단점
- 장점 뿐 아니라 단점도 있다. 코드가 길고 불필요한 private 키워드로 생성자를 제한하는 코드가 있다. 이로 인한 가독성이 떨어진다.
자바에서 별도의 Enum Type 탄생하였고 다음 글에서 살펴보자.