enum을 제대로 써보고자 정리해본다
Enum
몇몇개의 상수들을 묶어서 관리하고 싶을 때. 연관된 상수들의 집합을 표현할때 사용한다.
- 월, 화, 수, 목, 금, 토, 일
- 남자, 여자
- SKT, LGU+, KT
enum은 C, C++ 등에 있던 개념인데, 자바에서는 JDK 1.5 버전에서 도입되었다. 자바에 enum이 도입되기 이전에는, 이러한 집합을 표현할 때 다음과 같이 public static final 변수를 이용하였다.
public class TelecomCompany { public static final int SKT = 1; public static final int LG = 2; public static final int KT = 3; } public class Person { private String phoneNumber; // 휴대폰 번호 private int telecomCompany; // 휴대폰 통신사 }
이러한 방법의 문제점은..
1. type-safety 하지 않다. telecomCompany 변수에 TelecomCompany 클래스 안의 상수가 아닌 다른 int형 값이 할당될 수 있다.
2. print를 하면 의미없는 값이 출력된다. TelecomCompany.SKT를 프린트하면, 의미있는 이름인 "SKT" 대신 numeric value인 1이 출력된다.
3. 네임스페이스가 없다. (음 이건 대략은 알겠는데 정확히 뭘 말하는지 모르겠다ㅠㅠ )
* 이펙티브 자바의 30번째 항목에서도 int형 상수 대신 Enum을 사용하라고 하고 있다.
enum을 이용하면 다음과 같이 표현하는데, 위에서 언급된 문제점을 모두 해결할 수 있다.
public enum TelecomCompany { SKT, LG, KT } public class Person { private String phoneNumber; // 휴대폰 번호 private TelecomCompany telecomCompany; // 휴대폰 통신사 }
"enum" in java
- keyword.
- 클래스, 인터페이스와 유사한 하나의 Data Type.
- Enum 상수의 집합을 정의하는데 사용됨
- type-safety
- int형 변수처럼 switch문에서 사용 가능
- Enum 안에 열거된 상수들은 암묵적으로 public static final 임.
Enum의 내부 구조
public enum TelecomCompany { SKT, LG, KT }
위의 enum TelecomCompany은 내부적으로 아래와 유사한 형태로 컴파일된다.
final class TelecomCompany extends Enum〈TelecomCompany〉 { private TelecomCompany(String name, int ordinal) { super(name, ordinal); } public static TelecomCompany[] values(){ TelecomCompany atelecomcompany[]; int i; TelecomCompany atelecomcompany1[]; System.arraycopy(atelecomcompany = ENUM$VALUES, 0, atelecomcompany1 = new TelecomCompany[i = atelecomcompany.length], 0, i); return atelecomcompany1; } public static TelecomCompany valueOf(String name){ return (TelecomCompany)Enum.valueOf(enumexample/TelecomCompany, name); } public static final TelecomCompany SKT = new TelecomCompany("SKT", 0); public static final TelecomCompany LG = new TelecomCompany("LG", 1); public static final TelecomCompany KT = new TelecomCompany("KT", 2); private static final TelecomCompany ENUM$VALUES[] = {SKT, LG, KT}; }
- enum TelecomCompany는 하나의 클래스이며, java.lang.Enum 클래스를 상속받았다.
- enum 상수들은 내부적으로 public static final 필드로 선언되었으며, "TelecomCompany"는 자료형이다.
- enum 안에 열거한 순서대로 0, 1, 2 라는 int value가 생성자로 전달되고 있다.
- ENUM$VALUE[] 라는 배열 안에 열거한 enum 상수들이 열거한 순서대로 들어가있다.
- values() 메소드와 valuesOf(String) 메소드가 자동으로 생성되었다.
- value() 메소드는 Enum 안에 열거된 enum 상수들을 , Enum 안에 열거된 순서대로 list up된 array로 반환해준다. 즉, enum에 열거된 전체 상수들에 대해 iterate 하고 싶을 때 value() 메소드를 사용할 수 있다.
for(TelecomCompany telecom: TelecomCompany.values()){ System.out.println("TelecomCompany: " + telecom); }
- static valueOf(String arg) 메소드는 String을 Enum으로 변환하는데 메소드인데, 상위클래스인 java.lang,Enum의 메소드를 내부적으로 사용한다. TelecomCompany.valueOf("SKT")를 호출하면 TelecomCompany.SK가 반환된다. String에 해당하는 Enum 상수가 없다면 java.lang.IllegalArgumentException 익셉션을 던진다.
java.lang.Enum 클래스
위에서 본 것과 같이, 사용자가 정의한 enum 클래스는 내부적으로 java.lang.Enum 클래스를 상속받는다.
- java.lang.Enum 클래스는 Serializable, Comparable 인터페이스를 implements 했다.
- java.lang.Object 클래스의 메소드들을 모두 오버라이드 했다.
- java.lang.Enum 클래스의 소스를 보면 다음과 같다. 좀 길지만, 어렵지는 않다.
public abstract class Enum〈e extends enum〈e〉〉 implements Comparable〈e〉, Serializable { /** enum 상수 이름 */ private final String name; public final String name() { return name; } /** enum 상수가 enum 클래스 내에 열거된 순서 */ private final int ordinal; /** EnumSet, EnumMap과 같은 내부 자료구조에서 사용하기 위해 설계된 것으로, 일반적으로는 사용하지 않아야 한다. */ public final int ordinal() { return ordinal; } /** 사용자가 직접 호출할 수 없으며, enum 상수 선언시 컴파일러가 만들어낸 코드에 의해 내부적으로 호출된다. */ protected Enum(String name, int ordinal) { this.name = name; this.ordinal = ordinal; } /** * java.lang.Object의 toString() 메소드를 오버라이드하였는데, enum 상수의 이름을 리턴한다. * 사용자가 직접 오버라이드하여 원하는 형식으로 프린트되도록 사용할 수 있다. */ public String toString() { return name; } /** 동일한 enum 상수인지 여부를 리턴한다. * enum 상수는 내부적으로 public static final 이므로, == 연산자로 비교해도 문제가 없다. */ public final boolean equals(Object other) { return this==other; } public final int hashCode() { return super.hashCode(); } /** * enum은 싱글톤이므로, clone 되서는 안된다. * clone 되지 않음을 보장하기 위하여 clone() 메소드를 final로 오버라이드하고, * 항상 CloneNotSupportedException를 던지도록 되어있다. */ protected final Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); } public final int compareTo(E o) { Enum other = (Enum)o; Enum self = this; if (self.getClass() != other.getClass() && // optimization self.getDeclaringClass() != other.getDeclaringClass()) throw new ClassCastException(); return self.ordinal - other.ordinal; } public final Class〈e〉 getDeclaringClass() { Class clazz = getClass(); Class zuper = clazz.getSuperclass(); return (zuper == Enum.class) ? clazz : zuper; } public static 〈t extends enum〈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 classes cannot have finalize methods. */ protected final void finalize() { } /** prevent default deserialization */ 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"); } }
- java.lang,Object 클래스의 메소드들이 전부 오버라이드 되어있는데, toString() 외의 다른 메서드들은 전부 final로 오버라이드 되어 있다. 즉 toString() 이외의 메소드는 우리가 직접 오버라이드 할 수 없다.
- toString() 메소드가 오버라이드 되어있으므로 enum 상수를 프린트했을 때 int value가 아닌 SKT, LG, KT가 출력된다. 또한, final이 아니므로 사용자가 직접 오버라이드 가능하며, enum 상수를 원하는 방식으로 프린트할 수 있으므로 유용하다.
- name() 메소드는 특정 enum 상수를 String으로 변환하는 메소드이다. TelecomCompany.SK.name()를 호출하면 문자열 "SKT"가 반환된다. 오버라이드되어 있는 toString()과 동일하지 않냐고 생각할 수도 있지만, 사용자가 toString()클래스를 직접 오버라이드 하여 사용할 경우를 위한 메소드이다.
- ordinal() 메소드는 해당 enum 상수가 enum 클래스 내에 나열된 순서를 리턴한다. 순서는 0부터 시작한다. 단 내부적으로 사용하기 위해 고안된 메소드이므로, 일반적으로 사용하지 않는 것이 좋다고 한다. (이펙티브 자바 31번에도 나와있다.) 나열된 순서를 사용해야 할 때에는 Enum 내에 final int 필드로 선언하여 순서를 저장해놓고 사용한다.
- 더 자세히: http://docs.oracle.com/javase/7/docs/api/java/lang/Enum.html
Enum의 특징/장점
1. type-safety 하다. enum 클래스 안에 열거된 상수 외에 다른 값이 할당된다면 Type이 맞지 않기 때문에 컴파일 에러가 발생한다.
2. 자신의 namespace를 가진다.
3. switch문에서 사용할 수 있음.
4. enum 클래스 안에 새로운 상수를 추가하는 것이 쉬우며, 기존 코드에 영향이 없음.
5. 클래스, 인터페이스와 같은 하나의 Type이므로 변수, 메소드, 생성자를 선언할 수 있음. (C, C++ 등의 다른 언어에서의 Enum보다 더 파워풀한 이유이다. 다른 언어의 Enum은 단순 int형 상수와 마찬가지이나, 자바에서는 하나의 참조 자료형이다.)
6. enum 클래스 안의 생성자는 반드시 private 이여야 함. 안그러면 컴파일에러가 남. 즉 new 연산자로 인스턴스를 생성하는 것이 불가능하며, enum의 상수들은 오로지 enum 자신 안에서만 생성될 수 있다.
7. enum 상수가 코드 안에서 처음으로 call 되거나 참조될 때 Enum 인스턴스가 생성된다.
8. enum 클래스는 내부적으로 싱글톤을 보장한다. 이를 이용해, 어떤 클래스를 싱글톤으로 생성해야 할 경우 enum으로 선언하기도 한다.
- public enum EasySingleton{ INSTANCE; 이렇게 선언하고, EasySingleton.INSTANCE 이렇게 접근한다.
9. 각 enum 상수마다 특정 타입의 value를 지정할 수 있다.
public enum TelecomCompany { SKT(1), LG(2), KT(3) }
단 이것이 제대로 동작하려면 value를 저장할 변수와, 변수에 값을 셋팅해주는 생성자를 정의해줘야 한다. SKT, LG, KT는 TelecomCompany라는 타입의 인스턴스이다. 즉 SKT, LG, KT는 각각의 int value 값을 가진다. SKT(1)은 int value '1'을 파라미터로 받는 TelecomCompany 클래스의 생성자를 호출하는 것이다.
public enum TelecomCompany { SKT(1), LG(2), KT(3); private int value; private TelecomCompany(int value) { this.value = value; } }
10. enum 클래스 안에 추상메소드를 정의할 수 있는데, 각 enum 상수마다 구현을 해줘야 한다.
public enum TelecomCompany implements Runnable{ SK(1) { @Override public String homepage() { return "http://sk-telecom.co.kr"; } }, LG(2) { @Override public String homepage() { return "http://lg-uplus.com"; } }, KT(3) { @Override public String homepage() { return "http://olleh.co.kr"; } }; private int value; public abstract String homepage(); private TelecomCompany(int value) { this.value = value; } .............. }
11. enum 클래스 안의 enum 상수들은 암묵적으로 static final 이며, 한 번 생성된 이후에는 값을 바꿀 수 없음. 바꾸려 하면 컴파일 에러가 남.
12. enum 클래스 안의 enum 상수들은 static final 이므로, "=="를 이용하여 비교하여도 안전하다. (단, Object들을 비교할 때에는 항상 equals(), compareTo() 메소드를 이용하여 비교하자.)
15. Collection 패키지의 EnumMap, EnumSet 을 사용 가능하다.
16. enum은 일반 클래스와 마찬가지로 interface를 implements 할 수 있으며, 메소드를 override 할 수 있다. 단 클래스 상속(extends)은 불가능하다. (내부적으로 java.lang.Enum 클래스를 상속하고 있어서 아닐까?)
#References: http://javarevisited.blogspot.sg/2011/08/enum-in-java-example-tutorial.html
댓글0