본문 바로가기

자바

자바의 열거형 enum - 스터디 9차

이번 주제는 조금은 쉽지만, 유용하게 사용하는 열거형 enum에 대해서 알아보려고해요.

열거형은 다른 언어들과 마찬가지로 여러 상수를 관리할때 사용할 수 있어요.

enum을 정의하는 방법부터 enum에 어떠한 메서드들이 제공되고 있는지 보면 좋을 것 같네요.

 

 

enum은 2004년 자바 Java SE 5 부터 도입되었는데요. 이때가 어노테이션, 지네릭등 기능적으로 많이 추가되었는데, enum 클래스도 같이 추가되었다고 합니다.

 

oracle 공식문서부터 살펴 볼게요.

enum이 나오기 전에 계절을 예로 들면서 public static final로 상수값을 정의하는 enum pattern을 사용 했었다고 하면서 이 패턴에 대한 문제점으로 인해 나왔다고 서술하네요. 나름대로 풀이해보면서 정리 해볼게요.

 

1.   enum 클래스는 왜 생겨났을까?

기존 정수 열거형 패턴의 단점에 대해서

  • 타입이 안전하지 않음
    • Since a season is just an int you can pass in any other int value where a season is required, or add two seasons together (which makes no sense).
    • 풀이해보면 int enum은 타입이 명확하지 않아, 잘못된 값이 사용될 가능성이 있다 정도 인것 같네요.

 

  • 네임스페이스 없음
    • You must prefix constants of an int enum with a string (in this case SEASON_) to avoid collisions with other int enum types.
    • 상수 간 충돌을 방지하기 위해 SEASON_처럼 불필요한 접두어를 붙히는 문제가 있다라고 말하네요.

 

  • 깨지기 쉬움
    • Because int enums are compile-time constants, they are compiled into clients that use them. If a new constant is added between two existing constants or the order is changed, clients must be recompiled. If they are not, they will still run, but their behavior will be undefined.
    • int enum은 컴파일해야 하는 상수값이라서 열거형의 추가나 순서변경이 이루어지면 전체 다시 컴파일 해야 된다고 하네요.

 

  • 충분하지 않은 출력 정보
    • Because they are just ints, if you print one out all you get is a number, which tells you nothing about what it represents, or even what type it is.
    • int enum은 출력하면 값만 나오기 때문에, 해당값이 무엇을 의미하는지 알기 어렵다 정도로 이해할 수 있겠네요.

 

정리하면 안전한 타입, 이름 정의, 컴파일, 추가 출력 정보 등을 보완해서 열거형이라는 말답게 한데 묶어서 관리할 수 있는 enum 클래스가 생겼다고 이해하시면 될 것 같아요.

 

2.   enum 클래스를 정의하는 방법

자바에서 enum을 정의하는 방법은 아주 쉽습니다.

class 만들때와 같은데, class 대신 enum을 사용하면 됩니다. 딱히 생각나는 enum이 없기때문에 위 문서에 있던 그대로 사용해서 코드 작성해볼게요.

public enum Season {
    SPRING, SUMMER, FALL, WINTER;
}

 

enum은 순서가 작성하는 순서대로 자동적으로 0부터 시작하는 정수값이 할당돼요. SPRING은 0, SUMMER는 1이 되겠네요.

public class SeasonTest {

  public static Season getCurrentSeason() {
  
    Month currentMonth = LocalDate.now().getMonth();
    
    return switch (currentMonth) {
      case MARCH, APRIL, MAY -> Season.SPRING;
      case JUNE, JULY, AUGUST -> Season.SUMMER;
      case SEPTEMBER, OCTOBER, NOVEMBER -> Season.FALL;
      case DECEMBER, JANUARY, FEBRUARY -> Season.WINTER;
      default -> throw new IllegalArgumentException();
    };
  }

  public static void main(String[] args) {
    Season currentSeason = getCurrentSeason();
    switch (currentSeason) {
      case SPRING -> System.out.println("봄입니다. 꽃이 피는게 보이네요.");
      case SUMMER -> System.out.println("여름입니다. 해변과 뜨거운 태양이 생각나네요.");
      case FALL -> System.out.println("가을입니다. 단풍이 아름답네요.");
      case WINTER -> System.out.println("겨울입니다. 눈이 내리는게 보이네요.");
    }
  }

}

 

오늘 좀 추웠는데, 벌써 봄이네요. ㅎㅎ

간단하게 현재 계절을 추출하는 코드를 작성해봤는데요. 이처럼 코드를 작성하면 관련있는 상수값들을 편리하게 관리할 수 있게 되니 간단하지만 유용하게 사용할 수 있어요.

 

좀 더 다양한 시선에서 본 것들

enum을 사용한 싱글톤 패턴 🙃

싱글톤 패턴은 특정 클래스의 인스턴스(객체)가 하나만 생성되도록 보장하는 패턴을 말하는데요.

enum으로 안전하게 싱글톤 객체를 구현 할 수가 있어요.

 

일반적으로 간단하게 멀티쓰레드 환경에서 안전하도록 double checked locking 방식으로 싱글톤 패턴을 구현해볼게요.

public class Settings {

    private static volatile Settings instance;

    private Settings() { }

    public static Settings getInstance() {
        if (instance == null) {
            synchronized (Settings.class) {
                if (instance == null) {
                    instance = new Settings();
                }
            }
        }
        return instance;
    }
    
    public void display() {
        System.out.println("Displaying Settings");
    }

}

 

 

이번에는 enum으로 구현해볼게요.

public enum Settings {
    INSTANCE;

    // 여기에 Settings 클래스의 메서드와 필드를 정의 가능
    // 예를 들어, 설정 값을 저장하는 메서드 등이 있음
    
    public void display() {
        System.out.println("Displaying Settings");
    }
    
}

 

사용은 이렇게 불러와서 사용하시면 됩니다.

public class SettingsDemo {
    public static void main(String[] args) {
    
        // Settings 싱글톤 인스턴스에 접근
        Settings settings = Settings.INSTANCE;

        // 현재 설정 표시
        settings.display();
    }
}

 

간단해지죠? enum은 메서드, 필드도 추가 할 수 있기 때문에, 클래스 객체처럼 사용할 수 가 있습니다.

기존 싱글톤 클래스(double checked locking) 코드에서처럼 volatile 키워드, synchronized 블록 등을 사용하지 않아도 자동으로 스레드 안전성을 보장받을 있고, 또한 리플렉션 API를 통해서 싱글톤으로 만들어놓은 객체를 또 생성하는 것 조차 막을 수 있으니 안전하고 손쉽게 싱글톤 객체를 만들수 있게 되었습니다.

 

싱글톤 패턴의 리플렉션, 직렬화 및 복제를 방지에 대해서 알고 싶으신분들을 위해서 잘 정리된 링크 참조할게요.

https://www.geeksforgeeks.org/prevent-singleton-pattern-reflection-serialization-cloning/

 

How to prevent Singleton Pattern from Reflection, Serialization and Cloning? - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

 

3.   enum이 제공하는 메소드 "values()와 valueOf()"

enum은 추가로 유용한 메서드들을 제공하는데요.

values()와 valueOf() 메서드를 알아보고 추가로 어떠한 메서드들이 있는지 찾아볼게요.

 

values() 메서드

values() 메서드는 해당 enum 선언된 모든 상수값들을 배열로 반환합니다.

public static void main(String[] args) {
    for (Season value : Season.values()) {
      System.out.println("season = " + value);
      //      season = SPRING
      //      season = SUMMER
      //      season = FALL
      //      season = WINTER
    }
  }

 

valueOf() 메서드

valueOf() 메서드는 주어진 문자열 이름과 일치하는 enum 상수를 반환합니다.

문자열이 enum 상수 하나와 정확히 일치하지 않으면 IllegalArgumentException 발생하니 요것도 알아두시면 좋겠네요.

public static void main(String[] args) {
    Season season = Season.valueOf("SPRING");
    System.out.println("season = " + season);
    // season = SPRING
  }

 

name() 메서드

name() 메서드는 enum 상수의 이름을 문자열로 반환합니다.

public static void main(String[] args) {
    String name = Season.SPRING.name();
    System.out.println("name = " + name);
    // name = SPRING
  }

 

 

4.   java.lang.Enum 클래스를 들어가보면

Enum 클래스

 

모든 Java 언어 열거형의 공통 기본 클래스라고 하면서 java docs에 설명이 잘나와있네요.

저는 더 자세하게 객체를 보고 싶을때는 직접 코드로 들어가는데요.

위에서 언급한 특징대로 Serializable, Comparable을 구현한 것을 볼 수 있네요.

생소한 Contable도 보이는데, Constable 인터페이스는 Java 12에서 도입된 기능으로, 상수 (constant pool) 표현될 있는 값을 가진 타입을 나타내는데, 쉽게 말해 고정된 상수값으로 사용된다라고 보시면 될 것 같아요.

 

Java docs로 보시는게 가장 정확하지만, 참고할만한 링크도 첨부 할게요.

https://download.java.net/java/early_access/jdk22/docs/api/java.base/java/lang/constant/Constable.html

https://coderanch.com/t/734952/java/Constable-ConstantDesc-Interfaces-Introduced-Java

 

What Are Constable and ConstantDesc Interfaces Introduced in Java 12 (Java in General forum at Coderanch)

 

coderanch.com

 

5.   EnumSet은 무엇?

 

EnumSet Java 컬렉션에서 제공하는, enum 타입의 요소만을 포함할 있는 Set구현입니다. EnumSet Set 인터페이스를구현하므로, 중복을허용하지않는 특징을 가집니다. 조금 특별하게  enum 타입의 특성을 활용하여 일반적인 Set 구현체에 비해 훨씬 효율적으로 사용 할 수 있습니다. Set + Enum 이라고 볼 수 있겠네요.

 

EnumSet의 특징

  • 높은 성능
    • EnumSet의 모든 메서드는 산술 비트 단위 연산을 사용하여 구현 됩니다. 이로 인해 추가, 제거, 조회 등의 작업이 매우 빠릅니다.
  • 요소 타입 제한
    • EnumSet은 오직 종류의 enum 값들만 넣을 있습니다. 예를 들어, 만약 당신이 과일을 나타내는 enum 가지고 있다면, EnumSet에는 과일 enum 값들만 저장할 있고, 다른 타입의 값이나 다른 enum 값은 저장할 없습니다.
  • null safety
    • EnumSet에는 null 요소를 추가할 수 없습니다. null을 추가하려고 하면 NullPointerException이 발생합니다.
  • 예측가능한 순서
    • EnumSet은 enum 상수가 선언된 순서대로 요소를 반복합니다. 이는 일반적인 HashSet과 다르게 예측 가능한 순서로 값을 처리할 수 있습니다.

간단히 EnumSet을 만들어볼게요.

public enum Season {
  SPRING,
  SUMMER,
  FALL,
  WINTER;
}

public class EnumSetExample {
  public static void main(String[] args) {

    // 모든 계절을 포함하는 EnumSet 생성
    EnumSet<Season> allSeasons = EnumSet.allOf(Season.class);
    System.out.println("All seasons: " + allSeasons);
    // All seasons: [SPRING, SUMMER, FALL, WINTER]

    // 특정 계절만 포함하는 EnumSet 생성
    EnumSet<Season> warmSeasons = EnumSet.of(Season.SPRING, Season.SUMMER);
    System.out.println("Warm seasons: " + warmSeasons);
    // Warm seasons: [SPRING, SUMMER]

    // 특정 계절을 제외한 EnumSet 생성
    EnumSet<Season> coldSeasons = EnumSet.complementOf(warmSeasons);
    System.out.println("Cold seasons: " + coldSeasons);
    // Cold seasons: [FALL, WINTER]

    // 특정 범위의 계절을 포함하는 EnumSet 생성
    EnumSet<Season> rangedSeasons = EnumSet.range(Season.SUMMER, Season.FALL);
    System.out.println("Ranged seasons: " + rangedSeasons);
    // Ranged seasons: [SUMMER, FALL]

    // 비어 있는 EnumSet 생성
    EnumSet<Season> noSeason = EnumSet.noneOf(Season.class);
    System.out.println("No season: " + noSeason);
    //No season: []
  }

}

 

EnumSet에 대해 더 알고 싶으시다면, baeldung 블로그에 너무 잘 정리 되어 있으니, 아래의 링크를 참고하시면 감사합니다.

https://www.baeldung.com/java-enumset/

 

정리해보면,
EnumSet은 enum 타입의 상수들을 효율적으로 관리할 때 사용할 수 있습니다. 특정 조건에 따라 enum 상수들을 그룹화하거나, 상태 관리, 설정 옵션 등을 표현할 때 유용하겠네요. ㅎㅎ

 

Reference

[Enum]

https://docs.oracle.com/javase/8/docs/technotes/guides/language/enums.html

 

Enums

Enums In prior releases, the standard way to represent an enumerated type was the int Enum pattern: // int Enum Pattern - has severe problems! public static final int SEASON_WINTER = 0; public static final int SEASON_SPRING = 1; public static final int SEA

docs.oracle.com

https://www.baeldung.com/a-guide-to-java-enums

https://download.java.net/java/early_access/jdk22/docs/api/java.base/java/lang/constant/Constable.html

https://coderanch.com/t/734952/java/Constable-ConstantDesc-Interfaces-Introduced-Java

https://docs.oracle.com/javase/8/docs/api/java/util/EnumSet.html

 

EnumSet (Java Platform SE 8 )

A specialized Set implementation for use with enum types. All of the elements in an enum set must come from a single enum type that is specified, explicitly or implicitly, when the set is created. Enum sets are represented internally as bit vectors. This r

docs.oracle.com

https://techblog.woowahan.com/2527/

 

Java Enum 활용기 | 우아한형제들 기술블로그

{{item.name}} 안녕하세요? 우아한 형제들에서 결제/정산 시스템을 개발하고 있는 이동욱입니다. 이번 사내 블로그 포스팅 주제로 저는 Java Enum 활용 경험을 선택하였습니다. 이전에 개인 블로그에 E

techblog.woowahan.com

 

[EnumSet]

https://www.baeldung.com/java-enumset

 

[싱글톤 패턴]

https://www.geeksforgeeks.org/prevent-singleton-pattern-reflection-serialization-cloning/

 

How to prevent Singleton Pattern from Reflection, Serialization and Cloning? - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

 

 

자바의 enum에 대해서 정리해봤는데요.
enum은 기본적이지만 정말 유용하게 사용되니 잘 모르신다면 자세하게 한번 뜯어보는 것도 좋다고 생각합니다.
부족한 글 읽어 주셔서 감사합니다. 또한 잘못된 내용 있으면 지적해주시면 감사하겠습니다. 🙏

 

하얀종이개발자