-
[Java] Enum 내부 코드 확인하기백엔드/Java 2023. 2. 22. 08:34반응형
사다리 미션을 하는데 리뷰어님이 Enum 내부 코드를 통해 답변을 해준 것이 기억났다.
그래서 Enum 코드를 확인하는데 다른 내부 코드들보다 만만해보여서 해석을 해보기로 했다.
전체적인 코드는 아래와 같다.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859public abstract class Enum<E 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() && // optimiazationself.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;}@NotNullpublic static <T extends Enum<T>> T valueOf(@NotNull Class<T> enumType, @NotNull 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);}@SuppressWarnings("deprecation")protected final void finalize() { }private void readObject(ObjectInputStream in) throws 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 내용도 정리해야겠다.
반응형'백엔드 > Java' 카테고리의 다른 글
[Java, Concurrency] Compare and Swap 알고리즘 (CAS) (0) 2024.04.14 [Java] @BeforeAll과 static block의 차이 (0) 2023.03.01 [Java] 표준 어노테이션 (0) 2023.02.21 [Java] var는 사용해도 되는 것일까? (0) 2023.02.13 [Java] final을 자주 사용하는 이유 (2) 2023.02.12