열거형(Enum Type)

열거형(enum)은 여러 상수로 이루어진 고정 집합을 나타내는 특수 데이터 타입이라고 할 수 있습니다. 더 정확하게 말하면 자바 클래스의 특별한 한 종류라고 말할 수 있습니다. 보통 열거형은 아래와 같이 선언합니다. class, interface가 들어갔던 자리에 enum이 들어온 것을 눈여겨봐 주세요.

enum 이름 {
	상수1, 상수2, ..., 상수N
}

열거형을 사용하면 서로 관련 있는 상수를 논리적으로 그룹화할 수 있습니다. 예를 들어서 방위(동, 서, 남, 북)를 나타내려면 열거형을 아래와 같이 쓸 수 있습니다.

enum CardinalDirection {
	EAST, WEST, SOUTH, NORTH
}

열거형 필드 작명 관습

열거형 작명 규칙은 클래스와 동일하고(즉, 파스칼 케이스), 열거형 필드는 상수이므로 관례에 따라서 대문자로 작성합니다. 중간에 단어를 구분하고 싶은 경우 UPPER_SNAKE_CASE와 같이 언더바(_)를 사용해서 구분합니다(즉, 대문자 스네이크 케이스). 자바 튜토리얼 문서에서는 다음과 같이 말하고 있습니다.

... 열거형 필드는 상수이므로 열거형 필드의 이름은 대문자로 표시한다.(Because they are constants, the names of an enum type's fields are in uppercase letters.) ...

예를 들어서 신호등을 나타내는 열거형 TrafficLight는 RED, YELLOW, GREEN 상수 필드를 가집니다.

열거형이라는 이름의 클래스

상속과 구현

열거형은 암시적으로 java.lang.Enum 클래스를 상속하는데, 자바에선 클래스의 다중 상속을 허용하지 않으므로 열거형이 다른 열거형이나 클래스를 상속할 수는 없습니다.

/* final */ enum Season /* extends Enum<Season> */ {  
    SPRING, SUMMER, FALL, WINTER;  
}

public abstract class Enum<E extends Enum<E>>  
        implements Constable, Comparable<E>, Serializable {
    ...
}

열거형의 정의에 어긋나기도 하지만, 사실은 final이 달려 있어서 클래스가 열거형을 상속받을 수도 없습니다. 열거형에서 상속은 그냥 머릿속에서 잊어버리세요. 대신 아래와 같이 인터페이스를 구현할 수는 있습니다. 

enum Season implements Seasonal {  
    SPRING, SUMMER, FALL, WINTER;  
  
    @Override  
    public Season getSeason() {  
        return this;  
    }  
}
  
interface Seasonal {  
    Season getSeason();  
}

생성자

열거형은 클래스와 마찬가지로 생성자를 가질 수 있습니다. 생성자에 접근 제어자는 기본이 private이며, 생성자를 만들지 않으면 클래스처럼 디폴트 생성자가 만들어집니다. 생성자는 있지만 외부든 내부든 열거형을 인스턴스화할 수는 없다는 점을 기억해주세요.

enum Season {
	SPRING, SUMMER, FALL, WINTER;

	/* private */ Season() {
		// ...
	}
}

그럼 왜 생성자를 만드는 걸 허용했을까요? 이해를 돕기 위해서, 열거형은 사실 아래와 같이 생긴 클래스와 같습니다.

final class Season extends Enum<Season> {
	public static final SPRING = new Season();
	public static final SUMMER = new Season();
	public static final FALL = new Season();
	public static final WINTER = new Season();
	...
}

따라서 열거형의 필드를 아래와 같이 생성자로 초기화할 수도 있습니다.

public enum Season {
	SPRING("봄"), SUMMER("여름"), FALL("가을"), WINTER("겨울");

	private final String name;

	Season(String name) {
		this.name = name;
	}
}

덧붙여서 정적 필드이므로 equals() 대신에 == 연산자를 사용해서 비교해도 무방합니다.

Season season = ...;
if (season == Season.SPRING) {
	System.out.println("지금 계절은 봄입니다.");
}

필드와 메서드

계속 살펴보면서 열거형은 아래 예제와 같이 필드와 메서드를 가질 수 있다는 사실을 눈치챘을 것입니다. 이 부분은 클래스와 크게 다르지 않습니다.

public class EnumExamples {
	// 중첩 enum은 기본적으로 static이다.
	// 따라서 외부 클래스인 EnumExamples의 인스턴스화가 필요하지 않다.
    /* static final */ enum Season {
        SPRING("봄"), SUMMER("여름"), FALL("가을"), WINTER("겨울");

		// 변경될 일이 없는 필드는 반드시 final로 선언하는 버릇을 들이자.
        private final String name;

        Season(String name) {
            this.name = name;
        }

        public String getName() {
	        // 클래스와 마찬가지로 자기 자신은 키워드 this로 가리킨다.
            return this.name;
        }
    }

    public static void main(String[] args) {
        Season season = Season.SPRING;
        // season = SPRING
        System.out.println("season = " + season);
        // season.getName() = 봄
        System.out.println("season.getName() = " + season.getName());
    }
}

지원하는 메서드 살펴보기

values(): 열거형의 모든 값을 배열로 가져오기

이 메서드는 자바 튜토리얼 문서에 다음과 같이 나와있습니다.

... 컴파일러는 열거형을 만들 때 특별한 메서드 몇 개를 자동으로 추가합니다. 예를 들어서, 열거형의 모든 값이 들어있는 배열을 열거형에서 선언된 순서대로 반환하는 정적 values() 메서드가 있습니다. 이 메서드는 보통 for-each 구조와 함께 사용하여 열거형의 값을 반복합니다. ...

정리하면 컴파일러가 열거형에 자동으로 열거형의 모든 값이 선언된 순서대로 담긴 배열을 반환하는 정적 메서드를 추가해준다는 것입니다.

public class EnumExamples {
    public enum Operation {
        ADD, SUBTRACT, MULTIPLY, DIVIDE
    }

    public static void main(String[] args) {
	    Operation[] operations = Operation.values();
	    // 차례대로 ADD, SUBTRACT, MULTIPLY, DIVIDE가 출력된다.
        for (Operation op : operations) {
            System.out.println(op);
        }
    }
}

valueOf(): 해당 이름을 가진 열거형 상수를 가져오기

values()와 마찬가지로 컴파일러가 열거형에 추가해주는 정적 메서드입니다. 해당 이름은 열거형의 필드명과 정확히 일치해야 합니다. 예를 들어서 열거형 필드가 ADD인데 Operation.valueOf("add")로 쓰면 IllgalArgumentException 예외가 발생합니다.

public class EnumExamples {
    public enum Operation {
        ADD, SUBTRACT, MULTIPLY, DIVIDE
    }

    public static void main(String[] args) {
        Operation operation = Operation.valueOf("ADD");
        System.out.println("operation = " + operation);
    }
}

ordinal(): 열거형 상수의 순서를 가져오기

순서는 0부터 시작하며 선언 순서를 따라갑니다. 따라서 ADD가 0, SUBTRACT가 1, MULTIPLY가 2, DIVIDE가 3이 됩니다. 하지만 자바독에는 '대부분의 프로그래머들은 이 메서드를 사용하지 않을 것이다. 이는 java.util.EnumSet과 java.util.EnumMap 같은 정교한 열거형 기반 자료구조에서 사용하도록 만들어진 것이다.'라고 나와 있으니 사용을 가급적 피합시다.

public class EnumExamples {
    public enum Operation {
        ADD, SUBTRACT, MULTIPLY, DIVIDE
    }

    public static void main(String[] args) {
        Operation[] operations = Operation.values();
        for (Operation operation : operations) {
            System.out.println(operation.name() + ": " + operation.ordinal());
        }
    }
}