본문 바로가기
공부(~2016)/Java

Java - Enum

by soy; 2015. 8. 13.

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