Enum

백기선님의 유튜브로 진행하시는 스터디를 진행하며 올리는 정리 블로그입니다.



Java의 Enum도 기본적으로 c나 c++의 enum과 같은 목적을 위한 클래스JDK 1.5이후에 생긴 클래스이다.

잠깐 C언어 얘기를 하자면 C언어의 C99 이전에는 boolean타입을 제공하지 않았기 때문에 다음과 같이 사용하고는 했었다.

typedef enum _boolean {
    FALSE,
    TRUE
} boolean;

#define FALSE 0
#define TRUE 1

◾ Java에서의 Enum 특징

Enum 비교시에 값이 아닌 타입까지도 체크가 가능하고 Enum의 상수값이 재정의 되어도 다시 컴파일하지 않는다.



enum 정의와 사용

enum Money { DOLLAR, WON, POUND, EURO, YEN, YUAN }

enum 키워드를 이용하여 정의를 할 수 있고 정의를 어디에 하느냐에 따라 분류를 해보자면 3가지정도로 분류할 수 있다.


◾ 정의 방법

1. 별도의 Java 파일로 정의

[Money.java]

public enum Money {
    DOLLAR, WON, POUND, EURO, YEN, YUAN
}

[example.java]

public class example {
    public static void main(String[] args) {
        Money dollar = Money.DOLLAR;
        System.out.println(dollar);
    }
}

2. 클래스 안에 정의

public class example {
    public enum Money {
        DOLLAR, WON, POUND, EURO, YEN, YUAN
    }
    public static void main(String[] args) {
        Money dollar = Money.DOLLAR;
        System.out.println(dollar);
    }
}

3. 클래스 밖에 정의

enum Money {DOLLAR, WON, POUND, EURO, YEN, YUAN}

public class example {
    public static void main(String[] args) {
        Money dollar = Money.DOLLAR;
        System.out.println(dollar);
    }
}


정의 위치 IDE에 표시되는 Enum객체 폴더 구조
별도 파일 enum
클래스 내 in
클래스 외부 out

정의 위치에 따라 enum객체가 생성된 위치가 다른 것을 볼 수 있다.


◾ enum 사용

Money.DOLLAR와 같이 Enum이름.상수명으로 사용을 할 수 있으며 Money money = Money.DOLLAR와 같이 변수에 할당 해줄 수도 있다.


/*C에서의 enum*/

#include <stdio.h>

enum Money { DOLLAR, WON, POUND, EURO, YEN, YUAN }

int main() {
    enum Money dollar = DOLLAR;
    enum Money won = WON;

    if (dollar == 0) {
        printf("출력1");        //출력
    }
    if (won > dollar) {
        printf("출력2");        //출력
    }
    return 0;
}

위와 같이 C의 enum은 각 상수가 int형으로 저장되어 산술연산도 가능하고 아래와 같이 상수리터럴과 비교해도 같게 나오는 문제도 생긴다.

/*java에서 private final static 사용하여 상수 비슷하게 사용하기*/

 public class example {
        private final static int DOLLAR = 0;
        private final static int WON = 1;
        private final static int POUND = 2;
        private final static int EURO = 3;
        private final static int YEN = 4;

        public static void main(String[] args) {
            System.out.println(DOLLAR == 0);
        }
    }

Enum이 없을 때 Java에서도 이와 비슷하게 class에 private final static int를 이용해서 상수값을 정의 할 수 있었다.

하지만 이 방법은 가독성에도 좋지 않고 변수명이 겹칠 수 있거나 상수리터럴과 비교가 된다는 문제점이 있다.


/*enum 사용하여 상수 열거형 생성*/

public class example {
    enum Money {DOLLAR, WON, POUND, EURO, YEN, YUAN}
    public static void main(String[] args) {
        Money dollar = Money.DOLLAR;
        Money won = Money.WON;

        System.out.println(dollar > won);   //error
        System.out.println(dollar == 0);    //error
    }
}

Java의 Enum은 각 상수가 상수 그 자체로써 작동을 하고 자료형이 다르기 때문에 아래와 같이 비교하려고 하면 compile error가 뜬다.


◾ 상수에 다른 값 추가

enum Money {
    DOLLAR("달러"),
    WON("원"),
    POUND("파운드"),
    EURO("유로"),
    YEN("엔"),
    YUAN("위안");

    private String hanguelName;

    private Money(){}
    private Money(String hanguelName){this.hanguelName = hanguelName;}

    public String getHanguelName(){
        return hanguelName;
    }
}

public class example {

    public static void main(String[] args) {
        Money dollar = Money.DOLLAR;
        Money won = Money.WON;

        System.out.println(dollar.getHanguelName());
        System.out.println(won.getHanguelName());

    }
}

클래스의 생성자와 같은 방법으로 값을 할당 해줄 수 있으며, getter를 정의해서 값을 참조 할 수도 있다.

이때 생성자는 PRIVATE 속성으로 생성해주어야 한다.

enum타입은 고정된 상수들의 집합이므로 컴파일 타임에 모든 값을 알고 있어야하기 때문에 다른 클래스에서 동적으로 값을 정해줄 수 없기 때문에 생성자를 private로 설정해야 하고 final과 다름이 없어진다.



enum이 제공하는 메소드

◾ values()

Enum의 모든 상수를 배열로 만들어 반환해주는 함수

 public class example {
    public static void main(String[] args) {
        for(Money money : Money.values()){
            System.out.println(money);      //차례대로 출력
        }
        System.out.println(Money.values()[1]); //WON 출력
        System.out.println(Money.values() instanceof Object); //true
    }
 }

Money[]형태의 타입으로 반환이 된다.

◾ valuesOf()

매개변수로 String형이 오며 이 매개변수와 동일한 이름의 상수를 찾아 상수를 반환 하고 없다면 IllegalArgumentException 예외를 발생시킨다.

 public class example {
    public static void main(String[] args) {
        System.out.println(Money.valueOf("WON"));   //WON 출력
        System.out.println(Money.valueOf("Rupee")); //IllegalArgumentException error
    }
 }

◾ ordianal()

enum 상수가 0부터 시작하여 정의된 순서를 반환(int)하는 함수

public class example {
    public static void main(String[] args) {
        Money dollar = Money.DOLLAR;

        System.out.println(dollar.ordinal()); //0
    }
 }

ordinal은 Enum을 정의한 순서를 반환하기 때문에 c때처럼 상수라고 생각하며 사용하지 말자.

ordinal은 EnumSet이나 EnumMap, JPA에서 접근하기 위한 내부 함수로 개발자는 거의 사용할일이 없는 메서드이다.

정의 순서가 바뀌거나 값이 추가 되면 전혀 다른 결과가 나오기 때문이다.

그런 이유로 Spring Data JPA에서 default는 ordinal이기 때문에 Enum순서가 바뀐다면 값이 아예 달라진다.
컬럼으로 Enum을 사용하면 @Enumerated를 이용해 String으로 사용하자.



java.lang.Enum

enum 클래는 기본적으로 java.lang.Enum라는 부모 클래스를 상속 받고 있고 이는 Object를 상속받고 ComparableSerializable를 implements하고 있다.

protected Enum(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
}

protected형으로 생성자를 하나 가지고 있는데 이는 개발자가 호출할 수 없고 컴파일러에 의해 enum 키워드에 반응하여 생성하기 위함이다.

public String toString() {
    return name;
}

이 클래스 내에 있는 public 메소드 중 Override해서 사용할만한 메서드로 기본은 상수 이름을 반환하지만, enum을 생성시 추가 값을 지정하여 생성했다면 이를 Override해줄 수 있다.

◾ 컴파일 시의 객체 생성

final class Money extends Enum<Money> {
	private Money(String name, int ordinal) {
		super(name, ordinal);
	}

	public static final Status DOLLAR = new Status("DOLLAR", 0);
	public static final Status WON = new Status("WON", 1);
	public static final Status POUND = new Status("POUND", 2);
    public static final Status EURO = new Status("EURO", 3);
    public static final Status YEN = new Status("YEN", 4);


	private static final Status ENUM$VALUES[] = { DOLLAR, WON, POUND, EURO, YEN };
}

enum 키워드를 사용하여 열거형을 생성하면 컴파일 시에 위와 같이 정의가 된다.



EnumSet

Set 인터페이스를 구현한 것.

EnumSet은 abstract키워드가 앞에 붙어 객체 생성이 불가능하고 of()와 같은 추상 팩토리 메서드에서 사용하는 noneOf(class<E> elementType) 메서드가 존재하여 이를 통해 구현 객체를 받을 수 있다.

public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
        Enum<?>[] universe = getUniverse(elementType);
        if (universe == null)
            throw new ClassCastException(elementType + " not an enum");

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

noneOf() 메서드를 보면 알겠지만 EnumSet 객체를 반환하는 것이 아닌 이를 상속받은 클래스들을 반환하고 있다.

이 둘 클래스 모두 private속성이므로 직접 사용할 수 없다.

  • 사용할 크기에 맞게 적합한 구현 객체를 골라 준다.

  • Enum의 상수들을 하나하나 Set에 담는 행위를 피할 수 있다.

  • EnumSet 내부 표현은 비트 벡터로 표현된다. (상수 개수가 64개 이하라면 long변수 하나로 표현한다.)

  • Enum 상수가 선언된 순서, 즉 ordinal() 메서드의 반환된 순서로 순회한다.

  • EnumSet iterator는 약한 일관성을 유지하여 ConcurrentModificationException을 발생시키지 않는다.

  • null 요소는 삽입이 되지 않는다.

  • 동기화가 되지 않는다.

◾ 메서드

  • EnumSet.allOf(Class elementType) : 매개변수의 타입의 모든 요소를 포함한 EnumSet을 반환

  • EnumSet.noneOf(Class elementType) : 매개변수의 타입의 비어있는 EnumSet반환

  • EnumSet.of(E e1, E e2 …) : 지정한 매개변수를 포함한 EnumSet을 반환

  • 그 외 java.util.AbstractSet, java.util.AbstractCollection, java.lang.Object, java.util.Set으로 부터 메서드들을 상속받고 있다.

    • 중복 검사를 위한 equals(),hashCode() ,add(),remove(),size(), toArray() => Object[] 반환, iterator()등 사용 가능



EnumMap

Map 인터페이스를 구현한 것.

  • Enum을 Key로 이용하는 Map이다.

  • Enum 상수로 null을 갖을 수 없기 때문에 null을 key로 갖지 못한다.

  • Enum은 정해진 상수를 사용하고 단일 객체이기 때문에 해싱하지 않고 이미 순서가 정해져있어 성능이 좋다.

  • key와 value가 배열로 구성되어있다.

  • EnumMap의 key는 ordianl로 관리 되기 때문에 iterator는 약한 일관성을 유지하여 ConcurrentModificationException을 발생시키지 않는다.

  • 동기화가 되지 않는다.

◾ 메서드

  • 생성자 : new키워드를 이용해 생성할 수 있고 일반 Map들과 다르게 enum 타입을 파라미터로 넘겨주어야 한다.

    • Map<Money,Integer> map = new EnumMap<>(Money.class);
  • clear() : 모든 요소 삭제

  • get(Object key) : key에 해당하는 value 리턴

  • put(K key, V value) : map에 데이터 입력

  • remove(Object key) : 해당하는 key가 있다면 삭제

  • equals(Object object) : 기준 Map과 같은지 비교

  • containsKey(Object key) / containsValue(Object value) : 해당하는 key와 value 가 있다면 true 반환

  • size() : Map의 요소 개수 반환

  • keySet() : Map에 있는 모든 key들의 Set view를 반환한다. (모든 key들을 보여준다.)

  • values() : Map에 있는 value들의 Collection view를 반환한다. (모든 값들을 보여준다.)



Enum을 사용하면서 얻을 수 있는 장점

그래 Enum이 좋은건 알겠어. 그런데 실제로는 어떻게 사용할 수 있을까 했는데 우아한 형제들 기술블로그에 활용기를 통해 장점들을 설명해주셔서 좋았고 이펙티브 자바책을 사서 봐봐야겠다는 생각을 했다.



추가 내용

◾ type safety

Runtime이 아닌 Complie타임에 문제를 잡을 수 있는 것으로 JVM은 컴파일할때, 특정 데이터 타입을 알 수 있으나 RUN시에 특정 데이터가 존재 하지 않는다거나 하는 문제가 생길 수 있다.

Enum을 사용하면 오타방지를 할 수 있고, 컴파일타임에 에러를 잡아주기 때문에 디버깅도 쉬워진다.

◾ Enum에서 값 셋팅 팁

Enum의 값을 추가해줄 때 1,2,3같이 좁은 숫자의 범위가 아닌 10,20,30 같이 범위를 두고 선언해주자.

언제 어떤이유에서 Enum에 값을 사이에 추가 해줄지 모르기 때문이다.





◾ Reference

https://www.nextree.co.kr/p11686/

http://cris.joongbu.ac.kr/course/java/api/java/lang/Enum.html

https://javarevisited.blogspot.com/2014/03/how-to-use-enumset-in-java-with-example.html#axzz6kSbFjAwM