Enum 으로 선언한 클래스는 어떤 클래스의 상속을 자동으로 받게 되나요

1. enum 클래스라는 상수의 집합

앞장에서 배운 final로 String과 같은 문자열이나 숫자들을 나타내는 기본 자료형을 고정할 수 있습니다. 이렇게 고정된 값을 "상수", 영어로는 "constant" 라고 합니다. 그런데, 어떤 클래스가 상수만으로 만들어져 있을 경우에는 반드시 class로 선언할 필요는 없습니다. 이럴 떄 class 라고 선언하는 부분에 enum이라고 선언하면 "이 객체는 상수의 집합이다" 라는 것을 명시적으로 나타냅니다. enum 클래스는 어떻게 보면 타입이지만, 클래스의 일종입니다. 그래서 enum 클래스라고 부르면 됩니다. 한글로 번역해서 "열거형" 클래스라고 불러도 무방합니다. 

package c.enums;

public enum OverTimeValues {

    THREE_HOUR,

    FIVE_HOUR,

    WEEKEND_FOUR_HOUR,

    WEEKEND_EIGHT_HOUR;

}

예를 들어 어떤 회사의 평일 3시간 이상 5시간 미만일 때의 야근 수당과 5시간 이상일 때의 야근수당이 다르고, 주말 4시간 이상 8시간 미만일 때의 휴일 근무 수당과 8시간 이상일 때의 야근 수당이 있다고 합시다. 위의 예제 코드는 이 상황을 선언해 둔 것입니다. 여러분들이 이 4개의 상수에 관련된 값을 할당할 수도 있으나, 먼저 이렇게 선언만 한 경우를 살펴봅시다. enum 클래스에 있는 상수들은 지금까지 살펴본 변수들과 다르게 별도로 타입을 지정할 필요도, 값을 지정할 필요도 없습니다. 그냥 해당 상수들을 콤마로 구분하여 나열해 주면 됩니다. 

혹시나 해서 이야기하면, 이 enum 클래스인 OverTimeValues 파일의 확장자도 .java입니다. 또한 컴파일한 파일의 확장자도 .class입니다. 

이 enum 클래스를 가장 효과적으로 사용하는 방법은 switch 문에서 사용하는 것 입니다.

package c.enums;

public class OverTimeManager {

    public int getOverTimeAmount(OverTimeValues value) {

        int amount=0;

        System.out.println(value);

        switch(value) {

        case THREE_HOUR:

            amount=18000;

            break;

        case FIVE_HOUR:

            amount=30000;

            break;

        case WEEKEND_FOUR_HOUR:

            amount=40000;

            break;

        case WEEKEND_EIGHT_HOUR:

            amount=60000;

            break;

        }

        return amount;

    }

}

getOverTimeAmount() 메소드를 보면 OverTimeValues라는 enum 타입을 매개변수로 받고, 변수명은 value로 지정했습니다. 야근 수당을 리턴할 amount라는 int 타입을 선언하고 0을 기본값으로 선언하고, 가장 마지막 줄에 그 값을 리턴합니다. switch 내의 case에는 OverTimeValues에 선언한 상수값들로 분기하도록 하였습니다. 

그러면 OverTimeValues라는 enum 타입을 어떻게 getOverTimeAmount() 메소드에 전달할까요? 다음의 main() 메소드를 보면 쉽게 이해할 것입니다.

public static void main(String[] args){

    OverTimeManager manager = new OverTimeManager();

    int myAmount = manager.getOverTimeAmount(OverTimeValues.THREE_HOUR);

    System.out.println(myAmount);

}

별도의 생성자도 필요 없고, 그냥 enum을 넘겨주는 것이 아니라는 것을 알 수 있습니다. OverTimeValues.THREE_HOUR를 매개 변수로 넘겨주었습니다. 즉, "enum클래스이름.상수이름"을 지정함으로써 클래스의 객체 생성이 완료된다고 생각하면 됩니다.

보통은 위와 같이 사용하지만 이해를 돕기 위해서 코드를 풀어 쓰면 다음과 같습니다. 

OverTimeValues value = OverTimeValues.THREE_HOUR;

int myAmount = manager.getOverTimeAmount(value);

여기서 value라는 변수는 OverTimeValues라는 enum 클래스의 객체라고 생각하면 됩니다. 하지만, enum 클래스는 생성자를 만들 수 있지만, 생성자를 통해서 객체를 생성할 수는 없습니다. 

2. enum을 보다 제대로 사용하기

앞의 내용을 보면 "그냥 enum 타입을 넘겨서 항상 switch로 확인해야 할까? 그냥 enum 클래스 선언시 각 상수의 값을 지정할수 없을까?" 라고 생각할 수 있습니다. 당연히 enum 상수 값을 지정하는 것은 가능합니다. 단, 값을 동적으로 할당하는 것은 불가능합니다. 

package c.enums;

public enum OverTimeValues2 {

    THREE_HOUR(18000),

    FIVE_HOUR(30000),

    WEEKEND_FOUR_HOUR(40000),

    WEEKEND_EIGHT_HOUR(60000);

    private final int amount;

    OverTimeValues2(int amount){

        this.amount=amount;

    }

    public int getAmount() {

        return amount;

    }

}

앞의 예제와 다르게 각 상수들의 값이 지정된 것을 볼 수 있습니다. 상수 아래에는 amount라는 변수가 final로 선언되어 있습니다. 생성자에서 매개 변수로 넘겨받은 값을 할당할 때 사용됩니다. enum 클래스도 생성자를 사용할 수는 있지만, 생성자의 선언부를 보면 public 이라고 되어 있지 않습니다. 이렇게 enum 클래스의 생성자는 아무것도 명시하지않는 package-private와 private만 접근 제어자로 사용할 수 있습니다. 앞에서 사용한 enum 클래스는 생성자가 없는데 어떻게 동작하였을까요? enum클래스는 일반 클래스와 마찬가지로, 컴파일할 때 생성자를 자동을 만들어줍니다. 

1절에서 배운 예제에서는 enum 클래스 선언은 매우 간단하지만, 구현이 복잡해집니다. 또한 메소드를 호출하여 switch로 값을 간단히 할당하기는 했지만, 3시간 근무할 경우의 야근 수당이 만약 2,000원 오른다면 어떻게 될까요? 이 값이 항상 바뀔 수 있는 경우에는 원격 서버에 있는 값을 읽어오도록하면 큰 문제는 없을 것입니다. 그런데, 이번 절에서 알아본 enum 클래스의 경우 만약 야근 수당이 2,000원 오르면 자바 프로그램을 수정한 후 다시 컴파일해서 실행중인 자바 프로그램을 중지했다가 다시 시작해야 한다는 단점이 존재합니다. 하지만, 성능은 2절의 예제에서 배운 방법이 훨씬 좋을 것입니다.

3. enum 클래스의 부모는 무조건 java.lang.Enum 이어야 한다.

자바에서 일반 클래스들은 상속에 상속을 거쳐서 여러 부모 클래스를 가질 수 있습니다. 하지만, enum 클래스는 무조건 java.lang.Enum 이라는 부모 클래스를 가져야 합니다.  즉, 여러분들은 extends java.lang.Enum이라는 문장을 enum 클래스를 선언할 때 사용하지 않지만, 컴파일러가 이 문장을 추가해서 컴파일 합니다. 그러니 enum을 선언하여 여러분 마음대로 extends하면 안됩니다. 게다가, 누가 만들어 놓은 enum을 extends를 이용하여 선언할 수는 없습니다. Enum이라는 모든 enum 클래스의 부모가 어떻게 되어 있는지 살펴봅시다. Enum 클래스의 생성자는 다음과 같이 선언되어 있습니다. 

접근 제어자 메소드 설명
protected Enum(String name, int ordinal) 컴파일러에서 자동으로 호출되도록 해 놓은 생성자다. 하지만, 개발자가 이 생성자를 호출할 수는 없다.

여기서 name은 enum 상수의 이름이다. ordinal은 enum의 순서이며, 상수가 선언된 순서대로 0부터 증가합니다. Enum 클래스의 부모 클래스는 Object 클래스이기 때문에, Object 클래스의 메소드들을 모두 사용할 수 있습니다. 하지만, Enum 클래스는 개발자들이 Object 클래스 중 일부 메소드를 Overriding하지 못하도록 막아놓았습니다. 

메소드 내용
clone() 객체를 복제하기 위한 메소드다. 하지만, 이 메소드는 enum 클래스에서 사용하면 안 된다. 만약 호출될 경우엔 CloneNotSupportedException이라는 예외를 발생시키도록 되어 있다.
finalize() GC가 발생할 때 처리하기 위한 메소드다.
hashCode() int 타입의 해시 코드 값을 리턴하는 메소드다.
equals() 두개의 객체가 동일한지를 확인하는 메소드다.

여기서 clone()을 제외한 세개의 메소드는 모두 final로 선언되어 있으므로, 여러분이 Overriding 할 수가 없습니다. 추가로, hashCode()와 equals() 메소드는 여러분들이 사용해도 무방합니다. 그 중에서도 equals() 메소드를 가장 많이 사용합니다. 그리고, clone()과 finalize() 메소드는 사용하면 안 된다고 생각하면 됩니다. Object 클래스의 메소드를 Overriding한 마지막 메소드는 toString() 메소드입니다. 기본적으로 enum 변수에 이 메소드를 호출하면 상수 이름을 출력합니다. toString() 메소드는 Enum 클래스에서 Overriding한 Object 클래스의 메소드 중에서 유일하게 final로 선언되어 있지 않습니다.

다음은 Enum 클래스에서 선언한 메소드 입니다.

메소드 내용
compareTo(E e) 매개 변수로 enum 타입과의 순서(ordinal) 차이를 리턴한다.
getDeclaringClass() 클래스 타입의 enum을 리턴한다.
name() 상수의 이름을 리턴한다.
ordinal() 상수의 순서를 리턴한다.
valueOf(Class<T> enumType, String name) static 메소드다. 첫번째 매개 변수로는 클래스 타입의 enum을, 두번째 매개 변수로는 상수의 이름을 넘겨주면 된다.

여기에 있는 모든 메소드를 여러분들이 반드시 사용할 필요는 없습니다. 이 중에서 그나마 많이 사용되는 메소드가 compareTo() 메소드 입니다. Enum 생성자에서 말했지만, Enum이 선언된 순서대로 각 상수들의 순서가 정해집니다. compareTo() 메소드는 이 순서가 같은지, 다른지를 비교하는데 사용됩니다. 만약 같은 상수라면 0을, 그렇지 않고 다르면 순서의 차이를 출력합니다. 순서의 차이는 메소드의 매개 변수로 넘기는 상수 기준으로 앞에 있으면 음수(-), 뒤에 있으면 양수(+)를 리턴합니다. 

package c.enums;

public enum OverTimeValues2 {

    THREE_HOUR(18000),

    FIVE_HOUR(30000),

    WEEKEND_FOUR_HOUR(40000),

    WEEKEND_EIGHT_HOUR(60000);

    private final int amount;

    OverTimeValues2(int amount){

        this.amount=amount;

    }

    public int getAmount() {

        return amount;

    }

    public static void main(String[] args) {

        OverTimeValues2 value2 = OverTimeValues2.FIVE_HOUR;

        System.out.println(value2);

        System.out.println(value2.getAmount());

        

        OverTimeValues2 value3 = OverTimeValues2.THREE_HOUR;

        System.out.println(value2.compareTo(value3));

    }

}
Enum 으로 선언한 클래스는 어떤 클래스의 상속을 자동으로 받게 되나요
실행 결과1

가장 아래에 결과가 1이 나온 것을 확인할 수 있습니다. THREE_HOUR는 FIVE_HOUR 바로 앞에 선언되어 있습니다. 그러므로 FIVE_HOUR 는 THREE_HOUR 하나 뒤에 있으니 1을 결과로 출력합니다.

또한 enum 클래스에는 API문서에는 없는 특수한 메소드가 하나 있습니다. 바로 values()라는 메소드입니다. 이 메소드를 호출하면 enum 클래스에 선언되어 있는 모든 상수를 배열로 리턴합니다. 어떤 사수가 어떤 순서로 선언되있는지 확인하기 어려운 경우에 이 메소드를 사용하면 많은 도움이 될 것입니다. 

package c.enums;

public enum OverTimeValues2 {

    THREE_HOUR(18000),

    FIVE_HOUR(30000),

    WEEKEND_FOUR_HOUR(40000),

    WEEKEND_EIGHT_HOUR(60000);

    private final int amount;

    OverTimeValues2(int amount){

        this.amount=amount;

    }

    public int getAmount() {

        return amount;

    }   

    public static void main(String[] args) {

        OverTimeValues2[] valueList = OverTimeValues2.values();

        for(OverTimeValues2 value: valueList) {

            System.out.println(value);

        }

    }

}
Enum 으로 선언한 클래스는 어떤 클래스의 상속을 자동으로 받게 되나요
실행 결과2

values() 메소드가 선언되어 있기 때문에 이렇게 해 놓아도 전혀 문제되지 않습니다. 그 다음 for 루프에서는 배열에 있는 각각의 값을 value라는 변수에 할당 한 후 출력합니다.