안녕하세요. 얼마 전 라이브 코딩에서 코치님께서 사다리 미션에 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을 위한 메소드를 이용할 수 있습니다.
저는 주로 values()
를 사용합니다. 상수의 index를 반환하는 ordinal()
와 상수 이름을 반환하는 name()
, 이름으로 상수를 검색하는 valueOf()
는 사용해보지 않았습니다. 아래에서는 values()
와 valueOf()
를 다루어보겠습니다.
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);
}
Enum<T> valueOf(String name)
형태의 메소드입니다. 상수의 이름으로 상수를 들고오는 형태죠. 프로덕션 코드에서 굳이 이름을 이용해서 상수를 들고오는 코드는 작성해보지 않았습니다. Shape shape = Shape.valueOf("CLOVER")
보다 Shape shape = Shape.CLOVER
가 더 직관적으로 보였기 때문입니다. 활용할 수 있는 사례가 생기면 포스트를 재업로드 해보도록 하겠습니다.
상수와 가장 큰 차이점을 저는 필드라고 생각했는데요. 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이지만, 편하게 사용하기 위한 몇가지 제약도 있습니다.
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의 특징을 잘 살려 적용해보도록 할 예정입니다.
이넘이 생각하던 것 보다 많은 역할을 할 수 있군요 .. 다음미션에서 적용한 후기도 알려주세요