참고자료
- https://stackoverflow.com/questions/4709175/what-are-enums-and-why-are-they-useful
- https://itmining.tistory.com/149
- http://www.tcpschool.com/java/java_api_enum
- https://stackoverflow.com/questions/18471653/how-java-ensures-only-one-instance-of-an-enum-per-jvm
- https://docs.oracle.com/javase/7/docs/api/java/lang/Enum.html
- https://www.tutorialspoint.com/java/lang/java_lang_enum.htm
- https://www.baeldung.com/java-enumset
이 질문에 대한 해답을 내리기 위해, enum을 사용하지 않는 경우를 예시로 들겠다.
public class UseEnum {
public static void main(String[] args) {
// int 데이터를 통해 Color 나타내기
String intColor = ColorInt.getType(1);
// 객체를 통해 Color 나타내기
ObjectColor objectColor = ObjectColor.RED;
// enum을 통해 Color 나타내기
Color enumColor = Color.BLUE;
// switch (objectColor){
//
// }
switch (enumColor){
}
}
}
// 색 종류를 구별하기 위해 int 값을 사용한다.
// 1 : RED
// 2 : BLUE
// 3 : GREEN
// 사용자는 각 숫자가 어떤 값과 대응되는 지 알고 있어야 한다.
class ColorInt{
static String getType(int type) throws IllegalArgumentException {
if(type == 1){
return "RED";
}else if(type == 2){
return "BLUE";
}else if(type == 3){
return "GREEN";
}else{
throw new IllegalArgumentException();
}
}
}
class ObjectColor{
private ObjectColor(){}
static final ObjectColor RED = new ObjectColor();
static final ObjectColor BLUE = new ObjectColor();
static final ObjectColor GREEN = new ObjectColor();
}
enum Color{RED, BLUE, GREEN}
고정된 타입의 데이터들을 구분하여 사용하기 위한 클래스들과 Enum이다. ObjectColor
클래스와 Color
enum의 동작은 거의 유사하지만,
enum을 정의하기 위해 enum
키워드를 사용한다.
public enum Season{}
위와 같이 enum을 선언한 이후 내부에 enum 상수를 선언한다.
public enum Season{
SPRING,
SUMMER,
AUTUMN,
WINTER
}
enum 상수는 첫번째부터 0이 자동으로 설정되며, 이후 상수들은 1만큼 증가되어 상수값을 갖는다.
L0
LINENUMBER 4 L0
NEW com/jm/whyuseenum/Season
DUP
LDC "SPRING"
ICONST_0
INVOKESPECIAL com/jm/whyuseenum/Season.<init> (Ljava/lang/String;I)V
PUTSTATIC com/jm/whyuseenum/Season.SPRING : Lcom/jm/whyuseenum/Season;
L1
LINENUMBER 5 L1
NEW com/jm/whyuseenum/Season
DUP
LDC "SUMMER"
ICONST_1
INVOKESPECIAL com/jm/whyuseenum/Season.<init> (Ljava/lang/String;I)V
PUTSTATIC com/jm/whyuseenum/Season.SUMMER : Lcom/jm/whyuseenum/Season;
위 코드는 Season
enum을 컴파일 했을 때 생성되는 바이트코드이다. LDC "SPRING"
명령어를 통해 “SPRING”을 컨스턴트 풀에 집어넣고 있다. 기르고 ICONST_0
명령어를 통해 스택에 int value를 추가하고 INVOKESPECIAL
을 통해 생성자 메소드를 실행시킬때 “SPRING”
과 0
을 파라미터로 넘겨주고 있음을 알 수 있다. 즉, 실제 컴파일될 때 각 enum 상수에 대해서 int 값을 부여하고 있다.
public final enum com/jm/whyuseenum/Season extends java/lang/Enum{...}
자바에서 enum은 class로 취급된다. 위 코드는 바이트코드이며, Season
enum은 java.lang.Enum
클래스를 상속받고 있다. 따라서, enum은 JVM에 객체로서 존재하게 된다. 단! JVM에 하나의 enum 인스턴스만 존재하게 된다. 즉, enum은 싱글톤 패턴을 사용한다.
enum이 싱글톤 패턴임을 확인하기 위해 다시 바이트코드를 보자.
// access flags 0x4019
public final static enum Lcom/jm/whyuseenum/Season; SPRING
// access flags 0x4019
public final static enum Lcom/jm/whyuseenum/Season; SUMMER
// access flags 0x4019
public final static enum Lcom/jm/whyuseenum/Season; AUTUMN
// access flags 0x4019
public final static enum Lcom/jm/whyuseenum/Season; WINTER
enum 상수들은 static
으로 존재한다. 따라서, 해당 enum 클래스를 처음 사용하게 될 때, 클래스 로딩과정에서 처음 초기화가 이뤄진다. 클래스 로딩은 내부적으로 synchronized
하게 동작하므로, enum 인스턴스는 싱글톤임을 보장받을 수 있다.
enum 인스턴스는 premitive type
이 아닌 reference type
이므로, enum 타입 변수는 객체의 주소를 담는 reference type
이 된다. 이러한 이유는 enum 타입 변수는 null
일 수 있으며, 다른 객체와 비교될 때 해당 변수가 가리키고 있는 객체의 번지수를 기준으로 비교가 이뤄진다.
public class EnumVariableTest {
public static void main(String[] args) {
Season season = null;
season = Season.SUMMER;
// String과 Enum을 "=="를 통해 비교할 수 없다.
// System.out.println(season == "SUMMER");
System.out.println(season == Season.valueOf("SUMMER"));
System.out.println(season == Season.SUMMER);
System.out.println(season.compareTo(Season.SPRING));
// enum 상수는 내부적으로 int type 상수값을 갖고 있지만,
// 자바에서 enum 상수와 int 타입간의 비교를 지원하지 않는다.
// System.out.println(season == 1);
}
}
public enum Season {
SPRING,
SUMMER,
AUTUMN,
WINTER
}
// 출력
// true
// true
// 1
public class EnumValueTest {
public static void main(String[] args) {
SeasonWithValue seasonWithValue = SeasonWithValue.SUMMER;
System.out.println(seasonWithValue.getValue());
System.out.println(seasonWithValue.getWeather());
System.out.println(seasonWithValue.value == 1);
System.out.println(seasonWithValue.getWeather() == "hot");
}
}
public enum SeasonWithValue {
SPRING(0,"fresh"),
SUMMER(1,"hot"),
AUTUMN(2,"warm"),
WINTER(3,"cold");
final int value;
final String weather;
SeasonWithValue(int value, String weather){
this.value = value;
this.weather = weather;
}
public int getValue() {
return value;
}
public String getWeather() {
return weather;
}
}
// 출력
// 1
// hot
// true
// true
‘enum class’.values()
enum 클래스에 정의된 순서대로 enum 상수를 배열에 담아 반환한다.‘enum 클래스’.valueOf('문자열')
문자열에 해당하는 이름을 가진 enum 상수를 반환한다. 해당하는 enum 상수가 없을 경우 IllegalArgumentException
을 throw
한다.‘enum 상수’.compareTo('Object')
enum 상수와 입력받은 객체를 비교하여, enum 클래스의 enum 상수들의 순서상에서 enum 상수가 입력받은 객체보다 앞 순서이면 -1
, 동일한 객체이면 0
, 뒷 순서이면 1
을 반환한다. 만약, 입력받은 객체가 null
이거나 enum 클래스와 타입이 일치하지 않는다면 Exception
을 throw
한다.‘enum 상수’.name()
enum 상수가 enum 클래스에서 정의된 이름(String)을 반환한다. toString()
과 동일한 역할을 수행한다. 출력문에서 enum은 자동으로 String으로 전환되기 때문에, 특별히 enum 상수의 String 타입의 이름이 필요할 때 이 메소드를 사용한다.‘enum 상수’.ordinal()
메소드를 호출한 enum 상수가 enum 클래스에서 몇번째 순번에 있는지 해당 순번은 반환한다.public class EnumMethodTest {
public static void main(String[] args) {
Season season = Season.SUMMER;
for (Season item : Season.values()){
System.out.println(item);
}
System.out.println(Season.valueOf("SUMMER").getClass());
System.out.println(season.compareTo(Season.AUTUMN));
System.out.println(season.compareTo(Season.SUMMER));
System.out.println(season.ordinal());
System.out.println(season.name());
}
}
// 출력
// SPRING
// SUMMER
// AUTUMN
// WINTER
// class com.jm.whyuseenum.Season
// -1
// 0
// 1
// SUMMER
public abstract class Enum<E extends Enum<E>>
extends Object
implements Comparable<E>, Serializable
Enum
클래스는 모든 enum의 근본이되는 클래스이다. enum은 컴파일 될 때, Enum 클래스를 상속한다. Enum
클래스는 abstract
클래스이기 때문에 단독으로 인스턴스를 생성할 수 없다.
// Enum 클래스의 변수
private final String name;
private final int ordinal;
// Enum 클래스의 생성자
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
Enum
클래스는 단 하나의 생성자를 가지며, 해당 생성자에서 name
과 ordinal
을 매개변수로 받는다. name
은 enum 상수의 이름을 의미하며, ordinal
은 enum 상수가 enum 클래스에서 몇번째 순서인지를 나타내는 순번이다.
EnumSet
은 Enum
클래스와 함께 동작하기 위해 특화된 Set
자료형이다. 아래와 같은 상속 관계를 가지고 있다.
EnumSet
을 사용하고자 할 때 고려해야할 사항들이 존재한다.
enum value
만 포함할 수 있으며 모든 값들은 동일한 enum
에 속해야 함null
을 허용하지 않음thread-safe
하지 않기 때문에, 필요하다면 synchronized
하도록 만들어야 함EnumSet
에 저장되는 요소들은 enum
에 정의된 순서대로 저장된다.EnumSet
인스턴스가 수정되어도 문제없다.EnumSet
을 사용하는 것일까? 성능 상 이점이 있기 때문이다. EnumSet
의 구현체는 RegularEnumSet
과 JumboEnumSet
이 있다. RegularEnumSet
은 64bit의 long type의 bit vector를 사용한다. bit를 통해 Set 자료형을 구현한다. 비트연산을 수행하기 때문에 매우 빠른 속도를 보장한다. HashSet
과 비교하자면, HashSet
에서는 hashcode
를 계산해야 하지만, RegularEnumSet
은 비트연산을 통해 알아낼 수 있다.
64bit의 long type을 사용하기 때문에 64개 까지의 enum 상수들을 품을 수 있다. 만약 64개가 넘는다면 JumboEnumSet
을 사용하게 된다.
JumboEnumSet
은 RegularEnumSet
과 동일하게 64bit 데이터를 사용하지만, 여러 벡터들을 배열에 저장하기 때문에 큰 사이즈의 enum을 다룰 수 있다.
EnumSet
은 팩토리 메소드를 제공한다. 따라서, 직접 생성자를 호출하기 않고 팩토리 메소드를 통해 EnumSet
의 인스턴스를 얻을 수 있다.
public class EnumSetTest {
public static void main(String[] args) {
// Color enum에 속하는 모든 enum 상수를 포함 인스턴스 반환
EnumSet enumSet1 = EnumSet.allOf(Color.class);
print(enumSet1);
// RED YELLOW GREEN BLUE BLACK WHITE
// Color enum을 다루는 비어있는 인스턴스 반환
EnumSet enumSet2 = EnumSet.noneOf(Color.class);
print(enumSet2);
//
// of() 내부에 있는 enum 요소를 포함한 인스턴스 반환
EnumSet enumSet3 = EnumSet.of(Color.BLUE,Color.WHITE);
print(enumSet3);
// BLUE WHITE
// Color.YELLOW 부터 Color.BLACK까지 범위에 포함되는 enum 요소들을 포함한 인스턴스 반환
EnumSet enumSet4 = EnumSet.range(Color.YELLOW,Color.BLACK);
print(enumSet4);
// YELLOW GREEN BLUE BLACK
// complementOf()의 매개변수를 제외한 나머지 enum 요소들을 포함한 인스턴스 반환
EnumSet enumSet5 = EnumSet.complementOf(EnumSet.of(Color.BLUE,Color.BLACK));
print(enumSet5);
// RED YELLOW GREEN WHITE
// clone
EnumSet enumSet6 = EnumSet.copyOf(EnumSet.of(Color.BLUE,Color.BLACK));
print(enumSet6);
// BLUE BLACK
EnumSet enumSet7 = EnumSet.noneOf(Color.class);
enumSet7.add(Color.RED);
enumSet7.add(Color.BLUE);
print(enumSet7);
// RED BLUE
System.out.println("Set contain RED? " + enumSet7.contains(Color.RED));
// Set contain RED? true
enumSet7.forEach(System.out::print);
// REDBLUE
enumSet7.remove(Color.RED);
System.out.println();
enumSet7.forEach(System.out::print);
// BLUE
}
private static void print(EnumSet enumSet){
for(Object item : enumSet.toArray()) System.out.print(item + " ");
System.out.println();
}
public enum Color{
RED,YELLOW,GREEN,BLUE,BLACK,WHITE
}
}