Java

enum

donggi 2022. 10. 7. 23:49

 

백기선 자바 스터디 11주차 enum에 대해 학습하며 정리한 내용입니다.

 

열거형(enum)은 서로 연관된 상수들의 집합입니다.

열거형 상수인 enum은 자바 1.5버전부터 새롭게 추가되었습니다.

enum이란?

관련이 있는 상수들의 집합입니다. 자바에서는 final로 String과 같은 문자열이나 숫자들을

나타내는 기본 자료형의 값을 고정할 수 있습니다. 이렇게 고정된 값을 상수라고 합니다.

영어로는 constant입니다.

 

어떤 클래스가 상수만으로 작성되어 있다면 반드시 class로 선언할 필요는 없습니다.

이럴 때 class로 선언된 부분에 enum이라고 선언하면 이 객체는 상수의 집합이다라는 것을

명시적으로 나타냅니다.

 

enum은 enumeration이라는 셈, 계산, 열거, 목록이라는 영어 단어의 앞부분만 따서 만든 예약어입니다.

상수를 정의하는 방법

자바 1.5버전 이후에는 상수를 정의할 때 enum을 많이 사용합니다.

하지만 enum이 나오기 이전 다양한 방법으로 상수를 정의했습니다.

 

public class EnumEx {
	
    private final static int MONDAY = 1;
    private final static int TUESDAY = 2;
    private final static int WEDNESDAY = 3;
    private final static int THUSRDAY = 4;
    private final static int FRIDAY = 5;
    private final static int SATURDAY = 6;
    private final static int SUNDAY = 7;    
    
    public static void main(String[] args) {
    	int day = MONDAY;
        
        switch (day) {
        	case MONDAY:
            	System.out.println("월요일입니다. ");
                break;
            case TUESDAY:
            	System.out.println("화요일입니다. ");
            case WEDNESDAY:
            	System.out.println("수요일입니다. ");
                
                // ...
        }
    }
}

위의 소스 코드처럼 상수를 정의하여 사용할 수 있습니다.

하지만 상수를 추가해야한다면 한 눈에 보기에는 어렵습니다.

enum 정의하는 방법

public enum Day {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
}

enum 키워드를 이용하여 상수를 정의하는 소스코드입니다. 

기존에 상수를 정의하는 것보다 훨씬 더 간결하고 가독성이 좋습니다.

 

enum을 사용하는 이유는 다음과 같습니다.

  • 코드가 단순해지며 가독성이 좋습니다
  • 관리가 용이합니다
  • 데이터와 연관있는 상태와 행위도 한 곳에서 관리할 수 있습니다

enum의 열거형 상수에 추가 속성 부여

enum의 각 열거형 상수에 추가 속성을 부여할 수 있으며 추가 속성이 여러 개일 때 생성자에 순서대로

각각의 타입을 넣으면 됩니다. 

public enum EnumEx_3 {

    MONDAY("월요일", 10), TUESDAY("화요일", 20), WEDNESDAY("수요일", 30), 
    THURSDAY("목요일", 40), FRIDAY("금요일", 50),
    SATURDAY("토요일", 60), SUNDAY("일요일", 70);

    private String dayName;
    private int num;

    EnumEx_3(String dayName, int num) {
        this.dayName = dayName;
        this.num = num;
    }
}

추가 속성으로 String 타입과 int 타입의 속성을 추가하였습니다.

 

클래스 블럭 내에 추가 속성을 저장할 속성을 선언하고 생성자를 정의하면, 해당 상수가

추가적인 속성을 가질 수 있습니다.

 

enum의 생성자

Java에서 enum 타입은 열거형을 의미하는 클래스입니다.

그렇기 때문에 일반 클래스와 같이 생성자(Constructor)가 있어야 합니다.

 

물론 생성자를 만들어주지 않아도 Java가 default 생성자를 만들어주지만,

enum의 경우에는 다른 클래스들과 달리 일반적으로 생성자의 접근 제어자를 private으로

지정해야합니다.

 

enum 타입 클래스의 생성자를 일반적인 클래스 생성자와 같이 public으로 설정하거나

protected로 설정하게되면 다음과 같은 에러가 발생합니다.

enum 타입은 고정된 상수들의 집합으로써, 런타임(runtime)이 아닌 컴파일타임(compile-time)에 모든 값을 알고 있어야 합니다.

다른 패키지나 클래스에서 enum 타입에 접근하여 동적으로 어떤 값을 정해줄 수 없습니다.

 

따라서 컴파일 시에 타입 안정성이 보장됩니다. (해당 enum 클래스 내에서도 new 키워드로 인스턴스

생성이 불가능하며 newInstance(), clone() 등의 메서드도 사용 불가함)

 

이 때문에 생성자의 접근 제어자를 private으로 설정해야 합니다. 이렇게 되면 외부에서 접근 가능한 생성자가

없으므로 enum 타입은 실질적으로 final과 같이 동작하게 됩니다. 클라이언트에서 enum의 인스턴스를

생성할 수 없고, 상속을 받을 수도 없으므로 클라이언트의 관점에서 보면 인스턴스는 없지만 선언된 enum 상수는

존재하는 셈입니다.

 

결국 enum 타입은 인스턴스 생성을 제어하며 싱글톤(singleton)을 일반화합니다. 이러한 특성 때문에

enum 타입은 싱글톤을 구현하는 방법으로 사용되기도 합니다.

enum이 제공하는 메서드

compareTo(E o)

compareTo()의 매개변수로는 Enum 타입만 받을 수 있으며, 명시된 enum 사이의 순서를 비교하여

Integer 타입의 양수, 음수, 0을 반환합니다. 반드시 같은 종류의 enum 타입 상수만 비교가 가능합니다.

 

getDeclaringClass()

enum 상수의 enum 타입에 상응하는 클래스 객체를 반환합니다. 어느 enum에 속한 상수인지 알 수 있습니다.

 

name()

enum 키워드는 java.lang.Enum 클래스를 상속 받고 있습니다. Enum 클래스가 제공하는 메서드인

name()은 enum 상수의 이름을 반환합니다. 하지만 oracle에서는 대부분의 프로그래머들은 이 메서드보다

toString() 메서드를 사용한다고 강조하고 있습니다. name()은 final로 정의되어 override가 불가능하지만, toString()은 override

가능하여 유저 친화적인 이름을 반환할 수 있기 때문입니다.

 

ordinal()

enum 상수의 인덱스 번호를 반환합니다. 인덱스 번호는 0을 시작으로 상수가 정의되어 있는 순서대로 int 타입으로 반환됩니다.

enum에 정의된 상수의 순서가 바뀌면 인덱스 번호가 바뀌기 때문에 이 점을 유의하여 사용해야 합니다.

 

 

valueOf(Class<T> enumType, String name)

enum Hello {
    HELL;
}

public class EnumEx {

    public static void main(String[] args) {
        Hello.valueOf("HELL");
    }
}

Hello 라는 enum 클래스에 HELL 이라는 상수가 정의되어 있습니다.

valueOf() 메서드를 이용하여 enum 클래스에 인자 값과 같은 상수가 정의되어있는지 확인합니다.

 

상수가 정의되어 있다면 동일한 enum 상수 값을 반환하고, 상수가 없다면 IllegalArgumentException을 발생시킵니다.

 

values()

enum Hello {
    HELL, HOLL, HI, HOI;
}

public class EnumEx {

    public static void main(String[] args) {
        for (Hello value : Hello.values()) {
            System.out.println(value);
        }
    }
}

values() 메서드는 enum에 정의된 상수의 모든 값을 나열할 때 사용합니다. values() 메서드는 

java.lang.Enum 의 메서드가 아닌 컴파일러에 의해 자동으로 추가되는 메서드입니다.

java.lang.Enum

enum 클래스는 암시적으로 java.lang.Enum을 확장합니다. 클래스는 하나의 부모만 확장할 수 있고, Java는 상태의 다중 상속을 지원하지 않으므로 enum 클래스는 다른 클래스를 확장할 수 없습니다.

EnumSet

EnumSet은 enum 클래스로 동작하기 위해 특화된 Set(데이터를 중복 저장할 수 없고, 순서가 보장되지 않는 자료구조) 컬렉션입니다. Set 인터페이스를 구현하고 AbstractSet을 상속합니다.

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

 

EnumSet을 사용할 때는 몇 가지 고려할 사항이 있습니다.

  • 열거형 값만 포함할 수 있으며, 모든 값을 동일한 열거형이어야 합니다
  • null은 추가할 수 없으며 null을 추가하려고 할 시 NullPointerException이 발생합니다
  • thread safe 하지 않으므로, 필요할 경우 외부에서 동기화해야 합니다
  • 요소는 enum 클래스에서 선언된 순서에 따라 저장됩니다
  • 복사본에서 작동하는 fail-safe iterator(장애 발생시 작업을 중단하지 않음)를 사용하여 컬렉션을 순회할 때 
    컬렉션이 수정되어도 ConcurrentModificationException이 발생하지 않습니다

EnumSet은 추상 클래스로 인스턴스 생성을 위한 다양한 정적 팩토리 메서드가 정의되어 있습니다. JDK에서는 

비트 벡터(중복되지 않는 정수 집합을 비트로 나타내는 방식)를 지원하는 RegularEnumSet, JumboEnumSet 2가지의 EnumSet 구현체를 제공합니다.

 

RegularEnumSet은 하나의 long을 사용하여 비트 벡터를 나타냅니다. long의 각 비트는 열거형 값을 나타냅니다.

열거형의 i번째 값은 i번째 비트에 저장되므로 값이 있는지 여부를 쉽게 알 수 있습니다.

long은 64비트 데이터 유형이므로 이 구현은 최대 64개의 요소를 저장할 수 있습니다.

 

JumboEnumSet은 long 요소의 배열을 비트 벡터로 사용합니다. 이 구현은 64개 이상의 원소를

저장할 수 있습니다. RegularEnumSet과 거의 비슷하게 작동하지만 값이 저장된 배열 색인을

찾기 위해 몇 가지 추가 계산을 합니다.

 

EnumSet 팩토리 메서드는 enum의 원소 수에 따라 구현체를 선택합니다.

if (universe.length <= 64)
    return new RegularEnumSet<>(elementType, universe);
else
    return new JumboEnumSet<>(elementType, universe);

컬렉션에 저장될 원소의 수가 아닌 enum 클래스의 크기만 고려한다는 것을 명심해야합니다.

 

EnumSet 사용하기

EnumSet은 HashSet과 같은 Set 구현체이지만 값이 예측 가능한 순서로 저장되고 각 계산에

대해 한 비트만 검사하면 되므로 일반적으로 EnumSet이 더 빠릅니다. HashSet과 달리 버킷을

찾기 위해 해시코드를 계산할 필요가 없습니다.

 

또한 비트 벡터의 특성으로 EnumSet은 작고 효율적입니다. 이러한 이유로 EnumSet은 메모리를 덜 사용합니다.

 

EnumSet 대부분의 메서드는 인스턴스를 만드는 메서드를 제외하고 다른 Set처럼 작동합니다.

 

EnumSet을 생성하는 방법으로 allOf()noneOf()가 있습니다. 이렇게 하면 enum 의 모든 요소를

포함하는 EnumSet을 만들 수 있습니다.

public enum Color {
    RED, YELLOW, GREEN, BLUE, BLACK, WHITE
}

allOf()로 모든 요소를 포함하는 EnumSet을 만들 수 있습니다.

EnumSet.allOf(Color.class);

noneOf()를 사용하면 빈 Color 컬렉션을 갖는 EnumSet을 만들 수 있습니다.

EnumSet.noneOf(Color.class);

어디에 사용되나 궁금하여 찾아보니 allOf() 메서드를 실행하면 내부에서 noneOf() 를 사용하여 빈 컬렉션을 만든 뒤

모든 요소를 addAll() 해주고 있었습니다.

 

of()는 EnumSet에 들어갈 요소를 직접 지정하여 생성합니다.

EnumSet<Color> set = EnumSet.of(Color.YELLOW, Color.BLUE);

complementOf()를 사용하면 원하는 요소를 제거하고 EnumSet을 생성할 수 있습니다.

EnumSet<Color> set = EnumSet.complementOf(Color.RED);

 

copyOf()를 사용하면 다른 EnumSet의 모든 요소를 복사하여 EnumSet을 만들 수 있습니다.

of() 예시로 만든 set을 copyOf() 인자로 넣어 EnumSet을 생성하면 RED가 제거된 EnumSet이 생성됩니다.

EnumSet<Color> set2 = EnumSet.copyOf(set);

 

EnumSet은 왜 객체 생성이 불가능할까

EnumSet은 다른 컬렉션들과는 다르게 new 연산자 사용이 불가능합니다. 위의 예시처럼 정적 팩토리 메서드만으로

EnumSet의 구현 객체를 만들 수 있습니다.

 

이처럼 EnumSet은 abstract 키워드가 사용되기 때문에 객체 생성이 불가능합니다.

 

EnumSet의 구현 객체를 반환 받을 때 사용되는 noneOf() 를 살펴보면 내부적으로 EnumSet을 상속 받은

RegularEnumSet과 JumboEnumSet을 생성하여 반환해주고 있습니다.

 

이렇게 만든 이유를 살펴보면

 

1. 사용자 편의성 : 사용자는 어떤 구현 객체가 적합한지 몰라도 상관없다

RegularEnumSet은 원소의 개수가 적을 때 적합하고, JumboEnumSet은 원소의 개수가 많을 때 적합합니다. 이는

EnumSet의 구현체들을 모두 알고 있는 사용자가 아니라면 난해한 선택지가 될 수 있습니다. 하지만 EnumSet을 가장

잘 알고 있는 EnumSet 개발자가 적절한 구현 객체를 반환해준다면 EnumSet을 사용하는 입자에서는 어떤 구현체가

적합한지 고려하지 않아도 됩니다.

 

2. 사용자 편의성2 : 사용자는 빈번하게 발생되는 EnumSet 초기화 과정을 간단히 진행할 수 있다

EnumSet이 다루는 Enum의 모든 원소들을 Set에 담는 행위는 빈번하게 수행될 수 있습니다. 이러한 경우를 대비하여

EnumSet의 allOf() 를 사용하면 모든 Enum 원소가 담겨있는 EnumSet을 쉽게 반환 받을 수 있습니다.

 

3. EnumSet의 확장성과 유지보수의 이점

EnumSet을 유지보수하는 과정에서 RegularEnumSet과 JumboEnumSet 이외에 다른 경우를 대비하는 구현 클래스가

추가된다고 하여도 내부에 감추어져 있기 때문에 EnumSet을 사용하던 기존 코드에는 전혀 영향이 없습니다. 심지어

RegularEnumSet이 삭제된다 하더라도 사용자에게 영향이 없습니다. 이는 EnumSet의 확장성의 큰 이점으로 작용할 수 있습니다.

 

 

 

References

다양한 상수를 정의하는 방법 

https://stackoverflow.com/questions/13659217/where-is-the-documentation-for-the-values-method-of-enum

https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html

https://velog.io/@joomal/Java-Enum

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

객체 생성이 불가능한 EnumSet

'Java' 카테고리의 다른 글

멀티스레드 프로그래밍  (2) 2022.09.27
예외 처리  (0) 2022.09.15
Java의 Dynamic Method Dispatch, Abstract class, final, Object class  (0) 2022.09.01
Java Overriding  (0) 2022.08.30
Java의 상속과 super 키워드  (0) 2022.08.29