개발

[열거 타입] Enum 공부하다 생긴 의문점 2가지

오렌지색 귤 2022. 3. 2. 22:53
반응형

Enum 클래스

 

먼저 Enum 클래스의 코드를 살펴보겠습니다

 

public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {
    private final String name;
    private final int ordinal;

    public final String name() {
        return this.name;
    }

    public final int ordinal() {
        return this.ordinal;
    }

    protected Enum(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
    }

    public String toString() {
        return this.name;
    }

    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();
    }

    // Enum 클래스가 compareTo를 재정의하고 있다는 것을 봐두자
    public final int compareTo(E o) {
        if (this.getClass() != o.getClass() && this.getDeclaringClass() != o.getDeclaringClass()) {
            throw new ClassCastException();
        } else {
            return this.ordinal - o.ordinal;
        }
    }

    public final Class<E> getDeclaringClass() {
        Class<?> clazz = this.getClass();
        Class<?> zuper = clazz.getSuperclass();
        return zuper == Enum.class ? clazz : zuper;
    }

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

    protected final void finalize() {
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        throw new InvalidObjectException("can't deserialize enum");
    }

    private void readObjectNoData() throws ObjectStreamException {
        throw new InvalidObjectException("can't deserialize enum");
    }
}

 

생각보다 전체 코드의 길이가 길지 않아 모두 가져왔습니다.

 

 


궁금증 1. Enum<E extends Enum<E>>

 

첫 줄부터 말도 안되는 코드가 보였습니다!

 

열거 타입의 확장은 자바에서 금지되어 있습니다.

 

그래서 이펙티브 자바에서도 아이템 38에서 대부분의 열거 타입에는 확장을 해야할 이유가 없으며, 연산 코드와 같이 확장이 허용되어야 하는 특수한 상황에서는 Operation 인터페이스를 만들어 Enum 클래스인 BasicOperation과 ExtendedOperation이 Operation 인터페이스를 구현하도록 해야합니다.

 

더보기
// 인터페이스를 이용해 확장 가능 열거 타입을 흉내 냈다
public interface Operation {
	double apply(double x, double y);
}

public enum BasicOperation implements Operation {
	PLUS("+") {
		public double apply(double x, double y) { return x + y; }
	},
	... // 생략

	private final String symbol;

	BasicOperation(String symbol) {
		this.symbol = symbol;
	}

	@Override public String toString() {
		return symbol;
	}
}

// 확장 가능 열거 타입
public enum ExtendedOperation implements Operation {
	EXP("^") {
		public double apply(double x, double y) {
			return Math.pow(x, y);
		}
	};

	private final String symbol;

	ExtendedOperation(String symbol) {
		this.symbol = symbol;
	}

	@Override public String toString() {
		return symbol;
	}
}

 

근데 Enum<E>의 하위 타입이라니?? 불가능한 상황이 아닌가 하는 생각이 들 수 있습니다.

 

 

 

궁금증 1 해결

 

이 궁금증을 해결하려면 개념적으로 알아야 하는 것이 있습니다.

 

우선 Enum 타입은 모든 열거 타입의 common base class입니다.

 

자바에서는 각각의 열거 타입, 예를 들어 Color라는 열거 타입이 존재할 때, 해당 Color클래스는 Enum<Color>를 상속받고 있다고 판단합니다.

 

앞서 말했던 확장 불가능한 열거 타입은 Color인 것이지, Enum<Color>가 확장 불가능한 것은 아닙니다!

 

마치 모든 자바의 클래스들이 Object 굳이 상속받지 않더라도 컴파일 단계에서 자동으로 extends Object를 해버리는 것과 같은 상황입니다.

 

그리고 superclass Enum의 목적은 모든 열거 타입들로 하여금 공통적인 기능을 제공하기 위함입니다.

 

 

추가 궁금증

 

 

그렇다면 Enum<E extends Enum> 만으로도 충분하지 않을까 하는 생각이 들 수도 있습니다.

 

오히려 Enum<E extends Enum<E>> 구문은 자기 자신과 같은 타입 파라미터로 한정되는 새로운 Enum 클래스가 생성된다라고 잘못 해석되어 혼란스러움을 가중 시킬 수 있습니다.

 

하지만 다음의 설명을 통해 이를 자연스럽게 이해하실 수 있습니다.

 

 

추가 궁금증 해결

 

enum Color {RED, BLUE, GREEN};

 

위의 코드처럼 Color 열거 타입을 선언하게되면 컴파일러는 아래와 같은 코드로 이를 컴파일합니다.

 

public final class **Color** extends **Enum<Color>** {
    public static final Color[] values() {
        return (Color[])$VALUES.clone();
    }

    public static Color valueOf(String name) { ... }

    private Color(String s, int i) { super(s, i); }

    public static final Color RED;
    public static final Color BLUE;
    public static final Color GREEN;

    private static final Color $VALUES[];

    static {
        RED = new Color("RED", 0);
        BLUE = new Color("BLUE", 1);
        GREEN = new Color("GREEN", 2);
        $VALUES = (new Color[] { RED, BLUE, GREEN });
    }
}

 

위의 코드처럼 컴파일러에 의해 Enum<Color>를 구현하는 Color 타입은 Enum<Color>의 모든 메서드를 상속받게 됩니다.

 

해당 모든 메서드에는 Enum 클래스에 정의되어 있는 compareTo 메서드도 포함됩니다.

 

그리고 당연하게도 compareTo 메서드는 같은 타입끼리만 비교해야 하므로 Color 타입만을 인수로 받아야 합니다.

 

해당 타입만을 인수로 받게 하기위해서는 Enum이 generic 해야하며, Enum의 타입 파라미터인 E를 인수로 받아야 합니다.

 

 

 


궁금증 2. values() 메서드는 대체 어디있는가?

 

values() 메서드는 해당 Enum 클래스의 인스턴스 배열을 반환하는 메서드입니다.

 

Plant.LifeCycle[] values = Plant.LifeCycle.values();

for (Plant.LifeCycle value : values) {
    System.out.println("value.getClass() = " + value.getClass());
    System.out.println("value.name().getClass() = " + value.name().getClass());
}

/*
출력
value.getClass() = class doodle.item37.uncheckedTypeCast$Plant$LifeCycle
value.name().getClass() = class java.lang.String
value.getClass() = class doodle.item37.uncheckedTypeCast$Plant$LifeCycle
value.name().getClass() = class java.lang.String
value.getClass() = class doodle.item37.uncheckedTypeCast$Plant$LifeCycle
value.name().getClass() = class java.lang.String
*/

 

그런데 궁금한 것이 있습니다.

 

Enum 클래스에서는 사실 values() 메서드를 찾을 수가 없습니다.

 

심지어 getEnumConstants()getEnumConstantsShared(), enumConstantDirectory() 등의 일부 enum과 관련된 메서드들이 class Class<T>에 정의되어 있기에 해당 클래스에서도 찾아보았지만 없었습니다.

 

대체 존재하지도 않는 values() 메서드는 어떻게 불러오는 것일까요?

 

 

궁금증 2 해결

 

결론은 javadoc에서 Enum의 values()메서드는 찾을 수 없습니다.

 

그 이유는 컴파일 시점에 컴파일러에 의해 삽입되기 때문입니다.

 

It follows that enum type declarations cannot contain fields that conflict with the enum constants, and cannot contain methods that conflict with the automatically generated methods (values() and valueOf(String)) or methods that override the final methods in Enum (equals(Object), hashCode(), clone(), compareTo(Object), name(), ordinal(), and getDeclaringClass()).

- Oracle 공식 docs

 

 


PS

 

 

AngelikaLanger.com - Java Generics FAQs - Type Parameters - Angelika Langer Training/Consulting

Java Generics FAQs - Type Parameters Type Parameters © Copyright 2004-2022 by Angelika Langer.  All Rights Reserved. Type Parameters Fundamentals What is a type parameter? What is a bounded type parameter? A type parameter with one or more bounds.  The

www.angelikalanger.com

 

 

Chapter 8. Classes

class Point { int x, y, useCount; Point(int x, int y) { this.x = x; this.y = y; } static final Point origin = new Point(0, 0); } class Test { public static void main(String[] args) { Point p = new Point(1,1); Point q = new Point(2,2); p.x = 3; p.y = 3; p.u

docs.oracle.com

 

반응형