ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java] Enum 내부 코드 확인하기
    백엔드/Java 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 내용도 정리해야겠다.

    댓글

Designed by Tistory.