백엔드/Java

[Java] Enum 내부 코드 확인하기

70825 2023. 2. 22. 08:34
반응형

사다리 미션을 하는데 리뷰어님이 Enum 내부 코드를 통해 답변을 해준 것이 기억났다.

그래서 Enum 코드를 확인하는데 다른 내부 코드들보다 만만해보여서 해석을 해보기로 했다.

 

전체적인 코드는 아래와 같다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public abstract class Enum<extends Enum<E>> implements Comparable<E>, Serializable {
    
    private final String name;
    
    public final String name() { return name; }
 
    private final int ordinal;
 
    public final int ordinal() { return ordinal; }
 
    protected Enum(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
    }
 
    public String toString() { return 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(); }
 
    public final int compareTo(E o) {
        Enum<?> other = (Enum<?>) o;
        Enum<E> self = this;
        if (self.getClass() != other.getClass() && // optimiazation
            self.getDeclaringClass() != other.getDeclaringClass())
            throw new ClassCastException();
        return self.ordinal - other.ordinal;
    }
    
    @NotNull
    @SuppressWarnings("unchecked")
    public final Class<E> getDeclaringClass() {
        Class<?> clazz = getClass();
        Class<?> zuper = clazz.getSuperClass();
        return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
    }
 
    @NotNull
    public static <extends Enum<T>> T valueOf(@NotNull Class<T> enumType, @NotNull String name) {
        T result = enumType.enumConstantDirectory().get(name);
        if (result != nullreturn result;
        if (name == nullthrow new NullPointerException("Name is null");
        throw new IllegalArgumentException("No enum constant " + enumType.getCanonicalName() + "." + name);
    }
 
    @SuppressWarnings("deprecation")
    protected final void finalize() { }
 
    private void readObject(ObjectInputStream inthrows IOExceptions, ClassNotFoundException {
        throw new InvalidObjectException("can't deserialize enum");
    }
 
    private void readObjectNoData() throws ObjectStreamException {
        throw new InvalidObjectException("can't deserialize enum");
    }
}
cs

 

 

 

 

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


[abstract class]

추상 클래스이다. 코드를 살펴보니 여기에 추상 메서드가 없어서 상속을 받아도 따로 강제로 구현할 메서드는 없다.


[Enum<E extends Enum<E>>]

제네릭 코드가 섞여있는데, E는 Element(요소)라는 의미를 가지고 있다. 이런 코드를 가지게 되면 Enum의 하위 유형에 대해서만 인스턴스화를 할 수 있다는 것이다.

 

ex) enum Color { RED, BLUE, GREEN }

이것을 만들면 실제 컴파일러는 아래 코드로 이해를 하게 된다.

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 extends Enum<Color>를 통해 유용한 메서드를 상속 받으면서, 하위 유형인 Color.RED, Color.BLUE, Color.GREEN만 인스턴스화를 할 수 있도록 만든 것이다.

 

자세한 내용은 여길 참고하면 된다.


[implements Comparable<E>, Serializable]

Comparable<E>

여기서 E는 위에 나온 E와 같은 E를 뜻한다. Enum<Color>이면 Comparable<Color>로 들어간다는 것으로 Comparable 이름처럼 enum 하위 유형을 서로 비교할 수 있게 만드는 인터페이스이다.

Comparable<E> 내부 코드에 들어가보면 이걸 상속 받을 경우 compareTo를 구현하도록 만들었다. 스크롤을 위로 올려 Enum 코드를 확인해보면 compareTo가 정의되어 있는 것을 볼 수 있다.

 

Serializable

직렬화를 할 수 있다는 인터페이스로 직렬화란 JVM에서 객체에 저장된 데이터를 Byte 형태로 변환하는 기술이다.

이걸 사용하면 객체 데이터를 영속화할 수 있어 네트워크 통신을 통해 다른 곳으로 보낼 수도 있다.

영속화란 객체 데이터가 JVM 밖에서도 그대로 유지될 수 있도록 하는 것이다. (JPA에서 자주 나오는 영속화)

자세한 내용은 여기로


정리하자면 Enum 클래스는 추상 클래스로 직렬화와 하위 유형끼리 값 비교가 가능하다. 그리고 하위 유형만 인스턴스화를 할 수 있다.


 

 

 

2. name, ordinal, getName(), getOrdinal(), 생성자


 

ex) enum Color { RED, GREEN, BLUE }

name은 RED, GREEN, BLUE와 같은 상수 이름을 가지고 있다.

ordinal은 0부터 시작하는 상수의 순서를 뜻한다.

 

위에 있는 코드를 다시 가지고 왔는데, 지금 보면 무슨 말인지 이해가 갈 것이다.

우리가 사용하는 것보다는 enum 기반의 EnumMap, EnumSet과 같은 자료구조를 만들 때 사용한다고 한다.

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

 


 

 

 

3. 그 외 메서드


toString()

public String toString() {
	return name;
}

이건 sout(Color.RED)만 해도 RED로 잘 나와서 사용할 필요는 없다. 하지만 RED가 아닌 빨간색으로 출력을 한다던가, RGB값으로 출력하는 것처럼 가끔가다 재정의할 필요성이 있기 때문에 추가했다고 나와있다.


equals()

public final boolean equals(Object other) {
	return this==other;
}

하위 타입끼리 같으면 true를 출력하고, 아니면 false를 출력한다.

보시다시피 걍 하위 타입끼리 ==, !=로 비교해도 문제 없어 보인다.


hashCode()

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

해시값을 반환하는 메서드이다.


clone()

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

클론하지 말라고 만들었다고 한다.

보통 클론을 한다면 원본 객체와 클론한 객체가 서로 다른 객체로 사용하기 때문에 막아놨다.

그래서 enum을 인스턴스화하지 않기 떄문에 싱글톤 상태를 유지하게 만들어준다.


getDeclaringClass()

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

현재 상수가 어디 enum 클래스에 속하는지 반환한다. 만약 RED.getDeclaringClass()를 하면 COLOR가 나오게 된다.


compareTo(E o)

public final int compareTo(E o) {
	Enum<?> other = (Enum<?>)o;
	Enum<E> self = this;
	if (self.getClass() != other.getClass() && // optimization
		self.getDeclaringClass() != other.getDeclaringClass())
		throw new ClassCastException();
	return self.ordinal - other.ordinal;
}

서로 같은 enum클래스에 속하는지 확인한다. 만약 다르다면 ClassCastException()을 던진다.

반환값은 ordinal끼리의 차이값을 반환하고 있어서 하위 타입을 서로 비교할 일이 있다면 이 기능을 활용할 수 있도록 만드는 것이 좋다.


valueOf(Class<T> enumType, String name)

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

여기서는 내가 찾고 싶은 enum 클래스의 name을 검색하는 것이다.

예를 들면 valueOf(Color.class, "RED")로 Color.RED를 얻을 수 있다.

 

Color.valueOf("RED")랑 다른 것이다. Color.valueOf("RED")는 컴파일하면서 생겨나는 메서드이므로 여기에는 아직 존재하지 않는다. 글 위에 Enum<E extends Enum<E>>에 코드를 확인할 수 있다.


finalize()

@SuppressWarnings("deprecation")
protected final void finalize() { }

enum에서 finalize를 사용하지 못하게 막아놨다.

finalize()는 소멸자로 GC를 통해 Runtime Data Area의 Heap 영역에 저장된 객체에게 할당한 메모리를 해제한다.


readObject(ObjectInputStream in), readObjectNoData()

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


역직렬화를 할 수 없게 막아놨다. 만약 역직렬화를 하면 같은 enum이 동시에 존재할 수도 있어서 완전한 싱글톤 패턴을 유지하기 위해 적용된 것 같다.

직렬화를 가능하게 해둔건 데이터를 전송하기 위한 것 같다. 참고로 enum을 직렬화하면 상수 이름만 존재하고, 상수 이름의 필드 값은 나오지 않는다고 한다. 그래서 Spring에는 Jackson으로 해결을 할 수 있다고 하고, 여기서는 자바 코드만을 이용해 상수가 가지고 있는 필드값까지 가져온다.


 

 

 

코드도 짧고 만만해보였는데, 전혀 만만하지 않았다.

툭하면 JVM 내용이 나와서 빠른 시일내에 JVM 내용도 정리해야겠다.

반응형