Java 열거형 데이터 타입: Enum 101


[사계절 - 출처: TheImageStudio]
열거형(enumeration) 데이터 타입은 Java SE 5.0부터 지원된 것으로 "Effective Java"로 유명한 Joshua Bloch과 Neal Gafter가 설계하여 JSR-201에 포함된 내용입니다. 본 포스트에서는 열거형과 관련한 내용을 몇 단계 시리즈로 정리해봅니다.




때때로 특정 변수가 제한된 값들의 집합만 가져야 할 경우가 있는데 이때 사용하는 것이 열거형 데이터 타입입니다. 예를들어, 계절이나 옷의 사이즈에 해당하는 값들를 각각 {Spring, Summer, Fall, Winter}와 {Small, Medium, Large, XL, XXL} 등으로 정의할 수 있는데 이것을 코드로 작성하면 다음과 같습니다.
enum Season { SPRING, SUMMER, FALL, WINTER }
enum Size { SMALL, MEDIUM, LARGE, XLARGE, XXLARGE }
위 코드에서 Season과 Size는 열거형 데이터 타입이고, SPRING, SUMMER, ... , XXLARGE는 열거형 상수라고 합니다. 아래 코드는 Season 열거형의 간략한 사용예 입니다.
Season currentSeason = Season.SPRING;
Season nextSeason = Season.SUMMER;

System.out.println(currentSeason);

if(currentSeason == Season.WINTER)
    System.out.println("OOPS!");

if(currentSeason != nextSeason)
    System.out.println("Diff. seasons!");
SPRING
Diff. seasons!
currentSeason과 nextSeason은 Season 타입의 변수(참조변수)이고, 각각 SPRING과 SUMMER 상수로 초기화 했습니다. 먼저 currentSeason을 표준 출력으로 그대로 출력해 보니 열거형 상수의 이름(SPRING)이 출력되는군요. 두 번째로 currentSeason과 열거형 상수 WINTER를 동등비교 연산자로 비교해 보니 결과가 false임을 알 수 있습니다.



enum 키워드를 이용한 Season 선언문은 사실 클래스 선언문입니다. 특징은 정확히 네 개의 인스턴스(SPRING, SUMMER, FALL, WINTER)만 가지며 추가적인 Season 타입의 객체 생성할 수 없는 클래스입니다. Singleton 패턴으로 하나의 인스턴스만 가지는 클래스를 구현하듯 열거형 데이터 타입을 통해 n개의 인스턴스만을 가지는 클래스를 구현할 수 있습니다.

열거형 데이터 타입의 각 인스턴스는 "같음"을 비교할 때 equals 메서드를 사용할 필요없이 동등비교연산자(==)를 통해 비교해도 무관합니다. 아래 코드를 살펴봅시다.
Season prevSeason = Season.SPRING;
Season currentSeason = Season.SPRING;

if(currentSeason.equals(prevSeason))
    System.out.println("Same seasons-A");
if(currentSeason == prevSeason)
    System.out.println("Same seasons-B");  
Same seasons-A
Same seasons-B
prevSeason과 currentSeason은 모두 SPRING 객체를 가리키는 레퍼런스 참조변수이고 SPRING 객체는 오직 하나밖에 존재하지 않으므로 동등비교연산자로 비교해도 상관없습니다.

열거형 데이터 타입도 클래스의 일종이기때문에 생성자, 메서드, 필드를 가질수 있습니다. 다음 예제는 Size 열거형 데이터 타입을 확장하여 구현한것입니다.
public enum SizeEnum {

  SMALL("S"), MEDIUM("M"), LARGE("L");
 
  // Fields
  private String mAbbreviation;
  
  // Constructor
  private SizeEnum(String abbreviation) {
   mAbbreviation = abbreviation;
  }
  
  // Methods
  public String getAbbreviation() { return mAbbreviation; }
  public void setAbbreviation(String abbreviation) { mAbbreviation = abbreviation; }

}
특이한 점은 생성자의 접근 제한자가 private이며 이로인해 외부클래스에서는 명시적으로 SizeEnum(String) 생성자를 호출할 수 없다는 점입니다(public 생성자는 컴파일타임 오류!!). 간단한 SizeEnum의 사용예는 다음과 같습니다.
public class SizeEnumTest {

  public static void main(String[] args) {

    SizeEnum mySize = SizeEnum.SMALL;
  
    System.out.println(mySize);
    System.out.println(mySize.toString());
    System.out.println(mySize.getAbbreviation());
  
    mySize.setAbbreviation("SM");
    System.out.println(mySize.getAbbreviation());
  
    System.exit(0);
  }

}
SMALL
SMALL
S
SM
예제 코드에서 일반적인 클래스의 출력과 다른점은 mySize, mySize.toString()의 결과입니다. 일반적인 클래스라면 toString() 메서드를 재정의하지 않았기 때문에 "SizeEnum@xxxxxxx"과 같이 Object 클래스에서 정의된 toString()의 결과를 볼 수 있습니다. 하지만 열거형 객체의 toString() 결과는 열거형 상수의 이름을 출력합니다. 이것은 SizeEnum 클래스의 부모 클래스가 Object 클래스가 아닌 Enum(정확히 Enum<SizeEnum>) 클래스이고 Enum 클래스에서 재정의한 toString() 메서드를 호출했기 때문입니다.


그럼 toString() 메서드를 SizeEnum 클래스에서 다시 재정의해 볼까요?
public enum SizeEnum {
    SMALL("S"), MEDIUM("M"), LARGE("L");
 
    ...
    ...

    @Override
    public String toString() {
      return super.toString() + ": Abbre. = " + mAbbreviation;
    }

}
toString() 메서드 이 외에 Object 클래스의 메서드도 재정의 가능할까요? 아쉽게도 toString()을 제외한 나머지 메서드는 부모클래스인 Enum 클래서에서 final로 정의된 관계로 더 이상 재정의 할 수 없습니다.



마지막으로 Enum 클래스의 몇 가지 유용한 메서드를 살펴봅시다.

name() 메서드
public final String name()
Returns the name of this enum constant, exactly as declared in its enum declaration.
name() 메서드는 열거형 데이터 타입을 선언할 때 기술한 상수의 이름을 반환합니다. 일반적으로 toString() 메서드를 통해 상수의 이름을 문자열로 얻어올수도 있으나 toString() 메서드를 재정의한 경우 상수의 이름과 똑같은 문자열을 얻을 수 없기에 제공되는 메서드입니다.
SizeEnum mySize = SizeEnum.SMALL;

if(!"SMALL".equals(mySize.name()))
    System.out.println("Something wrong!!");

valueOf() 메서드
public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name)
Returns the enum constant of the specified enum type with the specified name.
valueOf() 메서드는 두 번째 인자인 문자열에 해당하는 열거형 타입의 객체를 반환합니다. 만약 주어진 이름과 똑같은 이름의 열거형 객체가 없거나 null을 인자로 주는 경우 예외가 발생합니다. valueOf() 메서드는 문자열 형태의 이름을 통해 객체를 찾고자 할 때 유용한 메서드입니다. 아래 코드는 상수의 이름이 "LARGE"인 열거형 상수객체를 찾아 반환하는 예입니다.
SizeEnum large = Enum.valueOf(SizeEnum.class, "LARGE");
System.out.println(large);

values() 메서드
public static T[ ] values()
Returns an array of all values of the enumeration.
values() 메서드는 열거형 타입의 모든 객체를 배열 형태로 반환합니다. 따라서 배열을 이용한 객체 참조에 유용하게 사용할 수 있습니다.
SizeEnum[] sizeArray = SizeEnum.values();

for(SizeEnum s : sizeArray)
  System.out.println(s.getAbbreviation());

모든 열거형 데이터 타입은 묵시적으로 두 개의 추가적인 정적메서드를 가지는데(Enum 클래스의 메서드가 아니기 때문에 API레퍼런스에서는 찾을 수 없음) 그 중 하나가 values() 메서드입니다.(Java Language Specification(SE7) 8.9.2 Classes)
In addition, if E is the name of an enum type, then that type has the following implicitly declared static methods: 
public static E[ ] values(); 
public static E valueOf(String name);

[참고문헌]
1. Core Java Vol.1: Fundamentals, Java SE6, 8th, Horstmann, 2008.

0 댓글