[java] enum 활용법 - 알아두면 좋은 TIP

서론

type이나 상태값을 나타내기위해 많은분들이 enum을 사용합니다.
enum을 활용하는 방법, 사용할때 알아두면 좋은 팁들을 정리해보려합니다.

예제

아래는 설명을 위해 사용할 예제입니다. 본문에 계속 사용될 예정입니다.
주문의 상태를 나타내는 enum입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public enum OrderStatus {
ORDER("주문"),
PAYMENT("결제완료"),
DELIVERY("배송"),
REJECT("거절"),
REFUNDED("환불"),
CANCEL("취소");

private String description;

TicketStatus(String description) {
this.description = description;
}
}

enum 기본기능 활용

name()

name() 를 통해 이름을 찾을 수 있습니다
이름을 따로 맴버변수로 등록할 필요가 없습니다.

1
2
String name = OrderStatus.DELIVERY.name();
log.debug("name : {}", name);

output :

1
name : DELIVERY

values()

Enum들을 모두 포함하고있는 배열을 얻을 수 있는 방법도 있습니다.

1
OrderStatus[] values = OrderStatus.values();

Java Collection의 List로 사용하고자 하신다면 Arrays.asList()를 사용하시면 됩니다.
stream등 collection의 기능을 활용하기 편리해집니다.

1
List<OrderStatus> orderStatuses = Arrays.asList(OrderStatus.values());

find by name

String으로 enum값을 찾는경우입니다.
String으로 된 변수에 이름이 들어가있고. 이 이름으로 enum값을 찾아야하는 경우가 있습니다.
이럴때는 매우 다양한 방법으로 구현할수 있는데요.

valueOf()

대표적으로는 enum의 기본기능을 활용하는 방법입니다.
valueOf()는 String으로 enum값을 찾아주는 대표적인 방법입니다.

1
2
3
4
5
6
7
8
public static OrderStatus trycatchValueOf(String name) 
try {
return OrderStatus.valueOf(name);
} catch (Exception ex) {
log.warn("Exception Thrown", ex);
return null;
}
}

NullPointerException 가발생하거나, 의도하지 않은 문자열인경우에 IllegalArgumentException이 발생하므로 try-catch 구문이 필요합니다. try-catch을 로직의 플로우로 사용하는것은 지양하기때문에 이방법을 추천드리지는 않습니다.

iterator 사용

또한 valueOf()의 경우 내부적으로 iterator를 사용하기때문에 모든항목을 하나씩 비교합니다. 때문에 O(n)의 시간복잡도를 가진다는 단점이 있습니다.

1
2
3
4
5
6
7
8
public static OrderStatus iterationFindByName(String name) {
for (OrderStatus status : OrderStatus.values()) {
if (name.equals(status.name())) {
return status;
}
}
return null;
}

시간복잡도를 줄이기위해서는 static HashMap을 미리 구성해놓는 대안이 있습니다.

[Guava] Enums.getIfPresent()

Google 에서만든 Guava 라이브러리는 우리가 원하는 기능을 완벽하게 지원하는데요
아래와같이 사용 할 수 있습니다

1
2
3
public static OrderStatus getIfPresent(String name) {
return Enums.getIfPresent(OrderStatus.class, name).orNull();
}

Json

아무런 설정을 하지않는다면 JsonFormat.Sahpe.String 으로 설정되어 되어있다면 아래와같이 출력되는데요.

1
2
3
{
orderStatus : CANCEL
}

api의 response를 통해 전송할때 json으로 변환하는경우가 습니다. description등 enum의 맴버변수까지 접근하길 원한다면 object형태로 전송할수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
...
public enum OrderStatus {
ORDER("주문"),
PAYMENT("결제완료"),
...;

private String description;
public String getDescription(){
return this.description;
}
}

output :

1
2
3
4
5
{
orderStatus : {
description : '취소'
}
}

enum의 name까지 필요한경우는 getter를 만들어줍니다.
JsonFormat.Shape.OBJECT에서 OBJECT를 만드는 기준은 getter입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum OrderStatus {
ORDER("주문"),
PAYMENT("결제완료"),
...;

private String description;
public String getDescription(){
return this.description;
}

String getName() {
return this.name();
}

}

output :

1
2
3
4
5
6
{
orderStatus : {
name: 'CANCEL'
description : '취소'
}
}

비교

equals() 로 비교할 필요 없이 == 로 비교하면 정상적으로 비교될 뿐 아니라 더 효율적입니다.
enum 은 singleton으로 생성되기때문에 어느곳에서 비교하더라도 값이 동등 할 뿐 아니라 동일하게 취급되기 때문입니다.

변수로 람다 지정하기

enum값에 따라 처리방법이 다르기때문에 분기처리하여 작업하는 경우가 자주 일어납니다.
경우에 따라서는 enum에서 달라지는 행동의 스팩을 정의하는 역할을 한다면 어떨까요?
enum의 값 하나하나마다 기능(함수나, 람다)를 담아두고 이를 호출하도록 합니다.

아래는 그 예시입니다.
예시는 이해를 돕기위해 사칙연산으로 준비했습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
enum Operation implements DoubleBinaryOperator {
PLUS("+") {
@Override
public double applyAsDouble(final double left, final double right) {
return left + right;
}
},
MINUS("-") {
@Override
public double applyAsDouble(final double left, final double right) {
return left - right;
}
},
MULTIPLY("*") {
@Override
public double applyAsDouble(final double left, final double right) {
return left * right;
}
},
DIVIDE("/") {
@Override
public double applyAsDouble(final double left, final double right) {
return left / right;
}
};

private final String symbol;

private Operation(final String symbol) {
this.symbol = symbol;
}

public String getSymbol() {
return symbol;
}
}

람다를 쓴다면 가독성을 훨신 좋게 만들 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
enum Operation implements DoubleBinaryOperator {
PLUS ("+", (l, r) -> l + r),
MINUS ("-", (l, r) -> l - r),
MULTIPLY("*", (l, r) -> l * r),
DIVIDE ("/", (l, r) -> l / r);

private final String symbol;
private final DoubleBinaryOperator binaryOperator;

private Operation(final String symbol, final DoubleBinaryOperator binaryOperator) {
this.symbol = symbol;
this.binaryOperator = binaryOperator;
}

public String getSymbol() {
return symbol;
}

@Override
public double applyAsDouble(final double left, final double right) {
return binaryOperator.applyAsDouble(left, right);
}
}

분류

특정 주문이 취소 가능한지 체크하는 로직이 있다고 가정해 봅시다.
enum으로 선언된 type으로 구별하려면 아래와 같이 할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public enum OrderStatus {
ORDER("주문"),
PAYMENT("결제완료"),
DELIVERY("배송"),
REJECT("거절"),
REFUNDED("환불"),
CANCEL("취소");

public static final List<OrderStatus> CANCELABLE_STATUS = Arrays.asList(ORDER,PAYMENT);

public static boolean isCancelableStatus(OrderStatus status){
return CANCELABLE_STATUS.contains(status);
}
}

참고자료

https://stackoverflow.com/questions/23361418/lambdas-in-the-classical-operation-enum-example
https://www.javacodex.com/ENUMs/Calculator-using-enums-for-operations

Comments