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 탄생하였고 다음 글에서 살펴보자.