본문 바로가기

프로그래밍 언어/자바

[Java] Enum

학습할 것 (필수)

  • enum 정의하는 방법
  • enum이 제공하는 메소드 (values()와 valueOf())
  • java.lang.Enum
  • EnumSet

참고자료

 

enum을 사용하는 이유는?

이 질문에 대한 해답을 내리기 위해, 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의 동작은 거의 유사하지만,

  1. enum을 사용했을때 더 간단하게 표현할 수 있다
  2. 또한, enum은 switch에 적용 가능하다
  3. singleton 패턴을 적용할 때 유용하다. enum은 한번만 생성된다.
  4. Thread safe하다. singleton 패턴 적용 시, 초기 객체를 생성할 때 여러 쓰레드가 동시에 접근하여 생성할 경우 잠재적 문제가 발생할 우려가 있다. enum은 자동으로 synchronized하게 생성되므로 thread safe하다.

 

enum 정의하는 방법

enum keyword

enum을 정의하기 위해 enum 키워드를 사용한다.

public enum Season{}

위와 같이 enum을 선언한 이후 내부에 enum 상수를 선언한다.

public enum Season{
    SPRING,
    SUMMER,
    AUTUMN,
    WINTER
}

enum 상수의 int value

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 값을 부여하고 있다.

 

enum 타입 변수

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

 

enum에 다른 값 연결

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이 제공하는 메소드 (values와 valueOf())

  • ‘enum class’.values() : enum 클래스에 정의된 순서대로 enum 상수를 배열에 담아 반환한다.
  • ‘enum 클래스’.valueOf('문자열') : 해당하는 enum 상수가 없을 경우 IllegalArgumentExceptionthrow 한다. 문자열에 해당하는 이름을 가진 enum 상수를 반환한다.
  • ‘enum 상수’.compareTo('Object') : enum 상수와 입력받은 객체를 비교하여, enum 클래스의 enum 상수들의 순서상에서 enum 상수가 입력받은 객체보다 앞 순서이면 -1 , 동일한 객체이면 0 , 뒷 순서이면 1 을 반환한다. 만약, 입력받은 객체가 null 이거나 enum 클래스와 타입이 일치하지 않는다면 Exceptionthrow 한다.
  • ‘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

 

java.lang.Enum

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 클래스는 단 하나의 생성자를 가지며, 해당 생성자에서 nameordinal 을 매개변수로 받는다. name 은 enum 상수의 이름을 의미하며, ordinal 은 enum 상수가 enum 클래스에서 몇번째 순서인지를 나타내는 순번이다.

 

EnumSet

EnumSetEnum 클래스와 함께 동작하기 위해 특화된 Set 자료형이다. 아래와 같은 상속 관계를 가지고 있다.

EnumSet 을 사용하고자 할 때 고려해야할 사항들이 존재한다.

  • 오직 enum value 만 포함할 수 있으며 모든 값들은 동일한 enum 에 속해야 함
  • null 을 허용하지 않음
  • thread-safe 하지 않기 때문에, 필요하다면 synchronized 하도록 만들어야 함
  • EnumSet에 저장되는 요소들은 enum 에 정의된 순서대로 저장된다.
  • copy본을 사용하기 때문에, iterable한 동작 중에 EnumSet 인스턴스가 수정되어도 문제없다.

그렇다면, 왜 EnumSet 을 사용하는 것일까? 성능 상 이점이 있기 때문이다.

EnumSet 의 구현체는 RegularEnumSetJumboEnumSet 이 있다. RegularEnumSet 은 64bit의 long type의 bit vector를 사용한다. bit를 통해 Set 자료형을 구현한다. 비트연산을 수행하기 때문에 매우 빠른 속도를 보장한다. HashSet 과 비교하자면, HashSet 에서는 hashcode 를 계산해야 하지만, RegularEnumSet 은 비트연산을 통해 알아낼 수 있다.

64bit의 long type을 사용하기 때문에 64개 까지의 enum 상수들을 품을 수 있다. 만약 64개가 넘는다면 JumboEnumSet 을 사용하게 된다.

JumboEnumSetRegularEnumSet 과 동일하게 64bit 데이터를 사용하지만, 여러 벡터들을 배열에 저장하기 때문에 큰 사이즈의 enum을 다룰 수 있다.

EnumSet의 메소드

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
    }
}

'프로그래밍 언어 > 자바' 카테고리의 다른 글

[Java] 인터페이스  (2) 2023.11.28
[Java] 패키지  (0) 2023.11.28
[Java] 상속  (0) 2023.11.21
[Java] 클래스  (1) 2023.11.21
[Java] 제어문  (0) 2023.11.21