Java

Java: enum 소개 및 API 파헤쳐 보기

teo_99 2023. 3. 5. 22:58

enum이 왜 필요할까?

enum이 필요한 이유는 무엇일까?

 

우리는 프로그래밍하다 보면, 어떤 값에 의미를 부여해야 할 때가 있다.

매직 넘버, 리터럴 등은 코드를 읽는 클라이언트가 의미를 파악하기 힘들다.

 

따라서 우리는 상수를 사용해 값에 적절한 의미를 부여하는데, 상수는 의미적으로 군집되어 있는 경우가 많다.

예를 들어, 요일 별 메뉴를 반환하는 식단표 어플리케이션을 생각해보자.


식단표 어플리케이션

// 식단표 어플리케이션
public String getMenu(String day) {
    if (day.equals("MONDAY")) {
    	return "순두부찌개";
    } else if (day.equals("TUESDAY")) {
    	return "짜장면";
    } else if (day.equals("WEDNESDAY")) {
    	return "볶음밥";
    } else if (day.equals("THURSDAY")) {
    	return "제육볶음";
    } else if (day.equals("FRIDAY")) {
    	return "서브웨이";
    }
    throw new IllegalArgumentException();
}

위와 같은 경우, "MONDAY", "TUESDAY".. 등과 같은 리터럴은 의미적으로 군집하다.

즉, 요일을 나타내는 리터럴이다.

 

이 경우 요일을 단순히 String 타입의 리터럴로 다루는 것이 맞을까?

메소드의 가장 마지막 줄에서도 눈치챘겠지만, 요일을 String 타입으로 다루는 것은 위험성도 존재한다.

메소드의 인자를 제한할 수 없기 때문이다. 즉, 값을 제한할 수 없다.

String 타입이라면 무엇이든 getMenu의 인자로 들어갈 수 있다.

 

enum을 사용하면 어떨까?

 

public enum Day {
    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY
}


// 식단표 어플리케이션
public String getMenu(Day day) {
    if (day.equals(Day.MONDAY)) {
    	return "순두부찌개";
    } else if (day.equals(Day.TUESDAY)) {
    	return "짜장면";
    } else if (day.equals(Day.WEDNESDAY)) {
    	return "볶음밥";
    } else if (day.equals(Day.THURSDAY)) {
    	return "제육볶음";
    } else if (day.equals(Day.FRIDAY)) {
    	return "서브웨이";
    }
    throw new IllegalArgumentException(); // 절대 실행되지 않음
}

인자로 들어오는 값들을 제한할 수 있게 되었고, 상수에 적절한 의미도 부여되었다.

 

이와 같은 방식과 마찬가지로 "순두부찌개", "짜장면"과 같은 리터럴도 Food라는 이름의 enum으로 개선할 수 있을 것이다.

 

이처럼 enum은 상수에 의미를 부여하고, 접근성을 제한하는 기능을 가진다.

또한 Java에서 enum은 상수에 행위를 부여할 수 있는 장점도 가진다.


enum API 파헤쳐보기

이번에는 Java 공식 reference를 통해 enum을 알아가보자!

 

우선, enum은 Object 직계의 추상 클래스이다.

 

이제는 enum의 필드 및 메소드들을 간략하게 알아보겠다.


enum 필드

enum 클래스의 필드에는 기본적으로 다음 두 인스턴스 변수가 존재한다.

private final String name;
private final int ordinal;

 

  • name

해당 enum 객체의 이름이다. 여기서 이름이란, enum 객체 선언 시 사용되었던 이름과 같다.

즉, Day.MONDAY 객체의 name 필드는 "MONDAY"가 된다.

 

  • ordinal

해당 enum 객체의 선언 순서이다. 쉽게 말해 인덱스라고 생각하면 된다.

다음과 같은 경우, Day.WEDNESDAY 객체의 ordinal은 2가 된다.

public enum Day {
    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY
}

하지만 이 oridinal 필드는 EnumSet이나 EnumMap을 위해 디자인되었기 때문에, 어플리케이션 개발자가 사용할 일은 거의 없다고 한다.

 


enum 메소드

  • name()
public final String name() {
    return name;
}

name 필드를 반환한다. 

하지만 Java에서는 일반적인 경우 이 name() 필드를 직접적으로 사용하기보다는,

toString()을 통해 보다 Programmer-Friendly하게 값을 반환하도록 재정의할 것을 추천하고 있다.

 

반면, 정확하게 이름을 아는 것이 목적인 경우에는 이 메소드를 사용해도 된다.

 

  • ordinal()
public final int ordinal() {
    return ordinal;
}

ordinal 필드를 반환한다.

앞서 말했듯, ordinal 자체가 EnumSet이나 EnumMap을 위해 고안되었기 때문에, 굳이 어플리케이션 개발자가 사용할 일은 없다.

 

  • toString()
public String toString() {
    return name;
}

 기본적으로 name 필드를 반환하지만, 필요한 경우 재정의해 Programmer-Friendly 하게 리터럴을 반환하게 할 수 있다.

 

  • equals(), hashCode(), clone()
public final boolean equals(Object other) {
    return this==other;
}

public final int hashCode() {
    return super.hashCode();
}

protected final Object clone() throws CloneNotSupportedException {
    throw new CloneNotSupportedException();
}

Object를 상속하기에 가지는 메소드들이다.

여기서 중요한 것은 1. equals가 동일성 비교를 한다는 점과 2. clone이 불가능하다는 점이다.

 

왜냐하면 enum 객체는 컴파일 타임에 생성되는 유일한 객체이기 때문이다.

이에 대해서는 나중에 따로 다루겠다.

 

  • valueOf()
public static <T extends Enum<T>> T valueOf(Class<T> enumType,
                                            String name) {
    T result = enumType.enumConstantDirectory().get(name);
    if (result != null)
        return result;
    if (name == null)
        throw new NullPointerException("Name is null");
    throw new IllegalArgumentException(
        "No enum constant " + enumType.getCanonicalName() + "." + name);
}

정적 팩토리 메소드이며, 해당하는 name의 객체를 반환한다.

이 시기에 name 필드가 유용하게 사용될 수 있다.

 

예를 들어, Day.MONDAY 객체를 가져오고 싶다면, 다음과 같이 하면 된다.

 

Day monday1 = Day.valueOf(Day.class, "MONDAY"); // 이 코드는
Day monday2 = Day.MONDAY; // 이 코드와 동일

 

  • values()

해당 enum 타입의 모든 객체들을 배열로 반환해주는 정적 메소드인데, 이 메소드의 경우 조금 특이하다.

API 공식 문서에도 나와있지 않고, Enum 객체에 정의되어 있는 메소드도 아니지만

enum 객체가 생성되면서 컴파일러가 생성해주는 메소드이기에 사용할 수 있다.

 

컴파일러가 values()를 생성해주는 이유는, Java 내부에서는 구현할 수 없는 로직을 담고 있기 때문이다.

 

이외에도 enum에는 다른 몇 가지 메소드들이 존재하지만, 필요할 때 알아보는 것이 좋을 듯 하다!


마치며

enum의 필요성을 알아보고, API를 파헤쳐보면서 보다 enum과 친해지는 시간을 가졌다.

 

다음에는 enum이 내부적으로 어떻게 구현되는지, 어떤 식으로 상태와 행위를 관리할 수 있는지 알아보려고 한다!

 


참고문서

https://docs.oracle.com/javase/7/docs/api/java/lang/Enum.html

https://stackoverflow.com/questions/13659217/where-is-the-documentation-for-the-values-method-of-enum