Java Enum은 "열거형" 정도로만 막연하게 알고 있었는데, 실무에서는 많이 그리고 유용하게 사용하는 듯 해서 이번 주차에는 Enum의 동작을 공부해보게 되었다.
코드들을 살펴보니, 보통은 다음과 같은 형태로 Enum을 사용하고 있었다.
public enum NetworkType1 {
HTTP("0001"),
TCP("0002"),
;
private String code;
NetworkType1(String code) {
this.code = code;
}
... // 그 외의 동작들
}
그 외의 동작들이란,
등을 찾아볼 수 있었다.
현재 코드에서는 찾아보지 못했지만, 인터넷에서 다른 코드들도 살펴보니 Enum 클래스에서 추상 메소드를 선언 -> Enum 타입에 맞게 각기 다른 일을 수행하도록 override 하는 경우도 자주 찾아볼 수 있었다.
예시
이렇게 Enum 별로 메소드를 override하는 코드로 확장시켜 생각해보면, 다음과 같은 코드를 알아두면 좋을 것 같았다.
public enum NetworkType1 {
HTTP("0001") {
@Override
public void print() {
System.out.println("Hello HTTP");
}
},
TCP("0002") {
@Override
public void print() {
System.out.println("Hello TCP");
}
},
;
private String code;
NetworkType1(String code) {
this.code = code;
}
public abstract void print();
}
위 코드에서 이해하기 어려웠던 부분은 크게 두 가지였다.
이 두 부분을 이해해주기 위해 jdk에서 Enum을 열어보게 되었다.
현재 External Libraries로 들어가있는 jdk 1.8에서 rt.jar > java > lang에 가보면 Enum 이라는 클래스가 있다.
public abstract class Enum< E extends Enum<E>> implements Comparable< E >, Serializable {
private final String name;
public final String name() { ... }
private final int ordinal;
public final int ordinal() { ... }
protected Enum(String name, int ordinal) { ... }
public String toString() { ... }
public final boolean equals(Object other) { ... }
public final int hashCode() { ... }
protected final Object clone() throws CloneNotSupportedException { ... }
public final int compareTo( E o) { ... }
public final Class< E > getDeclaringClass() { ... }
public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) { ... }
}
우선 가장 이해하기 어려웠던 부분은 Enum<E Extends Enum<E> 이었다. Enum<E Extends Enum>이라고 쓰면 안됐던 걸까?
다행히 같은 의문을 가진 사람이 쓴 질문글이 있어서, Enum의 내부 동작을 잘 설명해주는 좋은 글을 찾을 수 있었다.
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 });
}
}
결국 단순한 열거형으로 작성한 코드여도, 내부적으로는
compareTo()에 정확한 타입 파라미터를 넘겨주기 위해서 Enum<E Extends Enum<E>> 를 사용해주고 있음을 알 수 있었다. public final int compareTo(E o) {
Enum<?> other = (Enum<?>)o;
Enum<E> self = this;
if (self.getClass() != other.getClass() &&
self.getDeclaringClass() != other.getDeclaringClass())
throw new ClassCastException();
return self.ordinal - other.ordinal;
}
Enum<E Extends Enum<E>>는 Color Extends Enum<Color>로 컴파일되고 -> Enum<Color>에서 extends된 Color는 정확히 필요한 대로 type parameter인 E를 받아와 compareTo가 동작하고 있음을 알 수 있다.Enum<E Extends Enum<E>>)을 사용하고 있다.나아가, enum에서 열겨형으로 만들어졌던 RED, BLUE, GREEN의 값은 해당 enum 타입의 객체를 매번 생성하지 않더라도 Color.RED 와 같은 식으로 참조할 수 있어야 하고(=static), 한 번 할당된 변수의 값이 바뀌는 것을 허용하지 않기 때문에(=final) static final 형태로 객체를 만들어주고 있는 것이었다.
=> enum에서 열거형으로 만들어진 RED, BLUE, GREEN의 값은 (1) 해당 enum 타입의 객체를 매번 생성하지 않아도 Color.RED 이런 식으로 참조할 수 있어야 하고(=static), (2) 한 번 할당된 변수의 값이 바뀌는 것을 허용하지 않기 때문에(=final) static final 형태로 컴파일되는 듯 하다.
결국 정리하자면 Enum은 다음과 같은 동작을 하는 듯 하다.
Week thisWeek = Week.SUNDAY;
Week nextWeek = Week.SUNDAY;
thisWeek == nextWeek // true
위와 같은 코드가 있다고 생각할 때,



그렇기 때문에 위에서 본 코드에서 thisWeek == nextWeek이 가능한 것이었다. (같은 힙 영역의 객체를 참조하고 있음)
생글톤 형태로 어플리케이션 전체에서 사용되므로, public enum Rank {
THREE(3, 4_000),
FOUR(4, 10_000),
FIVE(5, 30_000);
private final int match;
private final int money;
private int count; // 가령 각 인스턴스의 count는 공유되고 있으므로, 멀티 스레드 환경에서 개발자가 예상하지 못한 에러를 발생시킬 수 있다!
Rank(int match, int money) {
this.match = match;
this.money = money;
}
public void plusCount() {
this.count++;
}
}
나아가서, enum 클래스의 멤버 변수들은 컴파일될 때에 static final로 객체가 각각 만들어지기 때문에, 가령 Enum 끼리 공유하는 변수가 있다면, 상속받은 클래스 간의 접근을 허가하는 protected를 사용해야 에러가 발생하지 않는 것이었다.
관련 예시
public enum NetworkType1 {
HTTP("0001") {
@Override
public void print() {
System.out.println("Hello HTTP");
}
@Override
public int getTypeCount() {
return typeCount;
}
},
TCP("0002") {
@Override
public void print() {
System.out.println("Hello TCP");
}
@Override
public int getTypeCount() {
return typeCount;
}
},
;
private String code;
// private int typeCount; <- 이렇게 private으로 해주면 컴파일 에러가 발생함
protected int typeCount;
NetworkType1(String code) {
this.code = code;
}
public abstract void print();
public abstract int getTypeCount();
}
공부한 것들을 바탕으로 java.lang.Enum을 참고해서 Enum을 만들어보긴 했는데, 그다지 이해에 큰 도움을 주는 것 같진 않았다...
public abstract class MyEnum<E extends MyEnum<E>> implements Comparable<E> {
static int index = 0;
String name;
String name() { return name; }
int ordinal;
int ordinal() { return ordinal; }
MyEnum(String name) {
this.name = name;
this.ordinal = index++;
}
public int compareTo(E other) {
return ordinal - other.ordinal;
}
}
public abstract class NetworkType2 extends MyEnum<NetworkType2>{
private String code;
// enum 사용시 => HTTP("0001"), TCP("0002");
public static final NetworkType2 HTTP = new NetworkType2("HTTP", "0001") {
@Override
public void print() {
System.out.println("Hello HTTP 2!");
}
};
public static final NetworkType2 TCP = new NetworkType2("TCP", "0002") {
@Override
public void print() {
System.out.println("Hello TCP 2!");
}
};
NetworkType2(String name, String code) {
super(name);
this.code = code;
}
public abstract void print();
}
다만 만들면서 추가적인 의문이 들었던 부분은,
추상 클래스면 인스턴스를 생성할 수 없는 클래스로 알고 있었는데... 돌이켜보니 new로 객체를 생성해주고 있다. (???)
데이터의 그룹화
상수를 한 곳에 모아두고 사용할 수 있다.