2018년 7월 31일 화요일

Java Enum 사용하기

 자바에서 제공하는 Enum 타입은 C++에서의 enum이 정수형 상수의 역할을 하는 것과는 달리 거의 완전한 클래스로서 새로운 타입을 정의하는 것과 같다. 덕분에 C나 C++ 같은 언어에 익숙한 개발자들이 처음 Java의 Enum을 접하게되면 약간 당황스러움을 느끼게 된다.

 그래서 이번 포스팅에서는 Java의 Enum을 이해하기 쉽도록 일반적인 Enum의 사용 코드 샘플을 보여주고 간단하게 설명 해볼까 한다.

 그럼 먼저 가장 간단한 형태의 Enum의 선언을 보자.

public enum Order {
    FIRST, SECOND, THIRD;
}

 지극히 간단한 Enum 선언 예제이다. 이렇게 선언된 Enum 타입을 어떻게 사용하는지는 아래 샘플 코드를 보면 간단하게 이해할 수 있다.

Order order = Order.SECOND;
...
// switch문
switch (order) {
    case FIRST:
        System.out.println("First !");
        break;
    case SECOND:
        System.out.println("Second !");
        break;
    case THIRD:
        System.out.println("Third !");
        break;
    default:
        System.out.println("Ooops !");
        break;
}

// if문
if (order == Order.SECOND)
    System.out.println("Second !");

 switch나 if문의 조건문에서 상수처럼 사용하는 일반적인 형태의 예제이다. 아마도 예상과 크게 다르지 않을 것이다.

 Java의 Enum은 거의 클래스와 같기 때문에 기본적으로 구현되어 제공되는 몇 가지 메소드가 있다. 실제 사용하다보면 바로 필요하게 될 것이므로 아래 예제 코드에서 사용하는 메소드의 출력 결과를 자세히 보고 숙지해 놓는게 좋겠다.

Order order = Order.SECOND;

System.out.println(order.toString()); // "SECOND"
System.out.println(order.name()); // "SECOND"
System.out.println(order.ordinal()); // 1

if (order == Order.valueOf("SECOND")) // Order.valueOf("SECOND") = Order.SECOND
    System.out.println("Same !!!");

for (Order val : Order.values()) // [FIRST, SECOND, THIRD]
    System.out.println(val);

 간단히 설명을 하자면...

  • toString()의 기본 구현은 해당 상수의 이름을 문자열로 반환한다.
  • name()도 기본 구현은 해당 상수의 이름을 문자열로 반환한다.
  • ordinal()은 해당 상수의 선언 순서에 따른 인덱스(Zero based)값을 반환한다.
  • valueOf()는 인자로 받은 이름과 같은 Enum값을 반환한다.
  • values()는 선언된 모든 Enum값을 순서대로 배열에 담아서 반환한다.
 Java의 다른 원시 타입과 함께 사용하다보면 종종 이런 메소드들이 필요하다. 하지만 매우 직관적이고 단순하므로 어려울 것은 없다. 다만 문제가 되는 것은 Enum의 기본 구현이 이런 메소드들을 제공한다는 사실을 잘 몰라서 헤메는 경우가 많으므로 반드시 숙지하기를 바란다. 이 메소드들은 overriding도 가능하므로 참고하도록 하자.

 기본적인 Enum 구현은 사실 문자열값과 함께 사용하기 편하지만 간혹 Enum의 값들이 내부적으로 정수값 같은 다른 값을 가지고 있기를 원하는 경우가 종종 있다. 이런 경우에 많이 사용하는 패턴의 샘플 코드를 보도록 하자.

public enum IntEnum {
    PENNY(1), NICKEL(5), DIME(10), QUARTER(25);

    private final int value;

    // Constructor
    IntEnum(int value) {
        this.value = value;
    }

    // Getter
    public int value() {
        return value;
    }

    public static IntEnum valueOf(Integer value) {
        if (value == null)
            throw new NullPointerException();

        if (value == 1)
            return PENNY;
        else if (value == 5)
            return NICKEL;
        else if (value == 10)
            return DIME;
        else if (value == 25)
            return QUARTER;

        throw new IllegalArgumentException();
    }
}

 처음 보여준 Order와 크게 다른 점은 내부적으로 int형 값을 가질 final 멤버 변수 'value'가 선언되어 있다는 것이다. 이렇게 final 멤버 변수를 하나 갖게 되면 Enum 상수를 정의할 때 그 값이 초기화가 되어야 하므로 반드시 생성자가 따로 추가되어야 하고 그 생성자는 반드시 private이어야 한다. 위 예제에서 PENNY, NICKEL 같은 Enum의 설계자가 정의한 값 말고 다른 값들이 외부에서 선언되어서는 안되므로 생성자는 당연히 private으로 선언되어야 할 것이다. 만약 public으로 선언을 하면 에러가 발생하므로 참고하길 바란다.

 멤버와 생성자가 추가되었으면 값을 선언할 때 인자로 정수값을 넘겨주어야 한다. 위 예제에서는 'PENNY(1)'과 같이 괄호와 정수 '1'을 값으로 넘겨서 선언하고 있음을 주목하자.

 이렇게 선언했다는 것은 당연히 다른 정수형 값들과 함께 사용하기 위함이므로 getter 메소드인 value()를 추가해주고 valueOf() 메소드를 overloading하여 문자열이 아닌 정수값으로 부터 Enum 값을 얻을 수 있도록 하였다. 반드시 필요한 건 아니지만 사용하다보면 금방 필요성을 느끼게 될 것이다.

 참고로 정수값을 보관하기 위한 'value'가 final로 선언된 이유는 실행중에 이 값이 바뀌어도 Enum값이 바뀌지는 않기 때문이다. 즉, 위 예에서 value의 final 키워드를 삭제하고 setter 메소드를 따로 추가한 후에 Runtime시에 PENNY의 value값을 10으로 바꿔줘도 PENNY는 여전히 PENNY일 뿐 DIME이 되지는 않는다. 아래 코드를 보면 이해가 쉬울 것이다.

IntEnum coin = IntEnum.PENNY;
coin.setValue(10); // coin = PENNY. not changed.
if (coin == IntEnum.DIME) // (false)
    System.out.println(coin); // not executed !!

 setValue(10)을 호출해도 PENNY인 coin 변수가 DIME으로 바뀌지는 않는다. 내부의 value값만 10으로 바뀔 뿐이다. 이렇게 value값이 바뀌게 되면 혼란만 초래할 뿐이므로 value는 final로 선언되는 게 맞으며 따라서 getter 메소드만 선언하면 되는 것이다. Java의 Enum이 거의 클래스와 같다고 하는 이유가 이런 멤버 변수와 메소드를 자유롭게 추가/변경할 수 있기 때문이지만 필요이상으로 남용하게되면 코드의 혼란만 야기할 뿐이다. 필자는 예제가 제시하는 수준의 사용이 적절하다고 생각한다. 그리고 위 예제에서는 int형을 예로 들었지만 다른 타입에 대해서 응용하는 건 이해만 제대로 하고 있다면 전혀 문제가 되진 않을 것이므로 따로 설명을 하진 않겠다.

 일반적인 상수의 사용보다 Enum을 사용하는 게 실제로 상당한 도움이 된다. Enum 사용이 익숙하지 않은 개발자들에게 간단한 샘플로서 많은 도움이 되기 바란다.

0 개의 댓글:

댓글 쓰기