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.