Java - Enum

13 August 2015

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

  • a 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를 지정할 수 있다. 단 이것이 제대로 동작하려면 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() 메소드를 이용하여 비교하자.)
  13. Collection 패키지의 EnumMap, EnumSet 을 사용 가능하다.
  14. enum은 일반 클래스와 마찬가지로 interface를 implements 할 수 있으며, 메소드를 override 할 수 있다. 단 클래스 상속(extends)은 불가능하다. (내부적으로 java.lang.Enum 클래스를 상속하고 있어서 아닐까?)

Reference: http://javarevisited.blogspot.sg/2011/08/enum-in-java-example-tutorial.html

Double Ended Linked List 마이크로 서비스 아키텍처