자바 Enum

후니팍·2023년 3월 9일
1
post-thumbnail
post-custom-banner

안녕하세요. 얼마 전 라이브 코딩에서 코치님께서 사다리 미션에 enum을 멋있게 활용하시더라구요. 저도 매번 우아한테크코스 미션을 하면서 enum을 활용했었는데요. 제가 사용한 enum은 그저 상수 덩어리구나~ 라고 느끼며 enum을 공부하게 되었습니다.


enum의 가장 단순한 형태

public enum Number {
    ACE, TWO, THREE, FOUR, FIVE, SIX, SEVEN,
    EIGHT, NINE, TEN, JACK, QUEEN, KING;
}

그저 상수의 집합처럼 보입니다. 일반 상수 final static과 다른 점은 "관련이 있는 상수들의 집합"이라는 점인데요. 그럼 단순히 관련이 있는 상수들만 한 곳에 모아둔 것일까요?

자바에서 enum은 클래스입니다. 자바만의 이런 enum 클래스는 아래와 같은 장점을 가집니다.
1. ide와 컴파일러가 오류를 미리 검증해줍니다.
2. enum을 위한 메소드를 이용할 수 있습니다.


enum을 위한 메소드

저는 주로 values()를 사용합니다. 상수의 index를 반환하는 ordinal()와 상수 이름을 반환하는 name(), 이름으로 상수를 검색하는 valueOf()는 사용해보지 않았습니다. 아래에서는 values()valueOf()를 다루어보겠습니다.

values()

enum 클래스에 있는 모든 상수값을 배열로 반환합니다. 저는 아래의 코드와 같이 플레잉 카드 52장을 생성할 때 사용했는데요, 이처럼 모든 enum 상수를 들고와야할 때 유용한 것 같습니다.

public static CardDeck create() {
    List<Card> cards = Arrays.stream(Shape.values())
            .flatMap(shape ->
                    Arrays.stream(Number.values()).map(number -> new Card(shape, number)))
            .collect(Collectors.toList());
    return new CardDeck(cards);
}

valueOf()

Enum<T> valueOf(String name) 형태의 메소드입니다. 상수의 이름으로 상수를 들고오는 형태죠. 프로덕션 코드에서 굳이 이름을 이용해서 상수를 들고오는 코드는 작성해보지 않았습니다. Shape shape = Shape.valueOf("CLOVER")보다 Shape shape = Shape.CLOVER가 더 직관적으로 보였기 때문입니다. 활용할 수 있는 사례가 생기면 포스트를 재업로드 해보도록 하겠습니다.


enum에 상태 추가

상수와 가장 큰 차이점을 저는 필드라고 생각했는데요. enum을 활용하면 아래와 같이 필드도 추가할 수 있습니다.

public enum Number {
    ACE("A", 1),
    TWO("2", 2),
    THREE("3", 3),
    FOUR("4", 4),
    FIVE("5", 5),
    SIX("6", 6),
    SEVEN("7", 7),
    EIGHT("8", 8),
    NINE("9", 9),
    TEN("10", 10),
    JACK("J", 10),
    QUEEN("Q", 10),
    KING("K", 10);

    private final String name;
    private final int value;

    Number(final String name, final int value) {
        this.name = name;
        this.value = value;
    }
}

필드를 활용해서 한 상수 안에 여러 상태값을 넣어줄 수 있는 것이 enum의 가장 큰 장점이라고 생각합니다. 상태가 추가되니 자연스레 상태를 위한 메소드를 만들어 활용하는 것도 가능합니다!

public enum Number {
	...

    public String getName() {
        return name;
    }

    public int getValue() {
        return value;
    }
}

싱글톤

enum의 또다른 특징은 인스턴스를 단 하나만 생성한다는 점입니다.

Number king1 = Number.KING;
Number king2 = Number.KING;

king1 == king2;  // true

위의 코드와 같이 KING을 여러개 생성했지만, enum은 하나의 인스턴스만 갖기 때문에 같은 KING으로 인식합니다. enum의 상수가 싱글톤과 같이 동작하는 것이죠!

위의 코드에서 equals()를 사용하지 않고 ==를 사용했는데, 같은 인스턴스이기 때문에 가능했습니다. 굳이 equals()를 오버라이드 하지 않아도 되는 점은 큰 장점이죠!


enum 조건

이렇게 편한 enum이지만, 편하게 사용하기 위한 몇가지 제약도 있습니다.

모든 필드가 불변이어야 한다

  • enum은 불변 클래스이기 때문에 필드 또한 불변이어야 합니다.

생성자에서 자신의 인스턴스를 Map과 같은 collection에 추가할 수 없다

  • 생성자를 호출한 시점은 static 필드들이 초기화 되기 전이기 때문입니다.
public enum Number {
    ACE("A", 1),
    TWO("2", 2);
    ...

    private final String name;
    private final int value;

    Number(String name, int value) {
        this.name = name;
        this.value = value;
        Number[] numbers = values(); // 이런거 못함
        valueOf("ACE"); // 이런거 못함
    }

    public String getName() {
        return name;
    }
}

에러는 다음과 같이 납니다

  • 첫번째 에러 : Caused by: java.lang.NullPointerException: Cannot invoke "[Lblackjack.domain.card.Number;.clone()" because "blackjack.domain.card.Number.$VALUES" is null
  • 두번째 에러 : Caused by: java.lang.IllegalArgumentException: blackjack.domain.card.Number is not an enum type

상수별 메소드

상수마다의 메소드도 정의할 수 있습니다

public enum Operation {
    PLUS {
        public int apply(int x, int y) {return x + y;}
    },
    MINUS {
        public int apply(int x, int y) {return x - y;}
    };
    
    public abstract int apply(int x, int y);
}

메소드의 인자가 1개인 경우 람다식과 함수형 인터페이스를 이용하여 코드를 더 간단하게 할 수도 있습니다.

public enum Operation {
    MULTIPLY_5 {
        public int apply(int x) {return x * 5;}
    },
    MULTIPLY_10 {
        public int apply(int x) {return x * 10;}
    };
    
    public abstract int apply(int x);
}
public enum Operation {
    MULTIPLY_5(value -> value * 5),
    MULTIPLY_10(value -> value * 10);
    
    private Function<Integer, Integer> expression;
    
    Operation(Function <Integer, Integer> expression) {
    	this.expression = expression;
    }
    
    public int calculate(int value) {
    	return expression.apply(value);
    }
}

마무리

enum을 잘 활용해보고 싶어 글로 남기게 되었습니다. 처음에 이동욱님께서 쓰신 글조차 이해하기 어려웠는데, 이제는 이해가 조금씩 되는 것 같습니다. 다음 미션에서는 enum의 특징을 잘 살려 적용해보도록 할 예정입니다.

profile
영차영차
post-custom-banner

2개의 댓글

comment-user-thumbnail
2023년 3월 9일

이넘이 생각하던 것 보다 많은 역할을 할 수 있군요 .. 다음미션에서 적용한 후기도 알려주세요

답글 달기
comment-user-thumbnail
2023년 3월 10일

디투 좋은데요?

답글 달기