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로 객체를 생성해주고 있다. (???)
데이터의 그룹화
상수를 한 곳에 모아두고 사용할 수 있다.