Java Enum은 Java 5에서 처음 도입된 특별한 클래스 타입으로, `java.lang.Enum` 클래스를 확장한다. Enum은 코드 가독성을 높이고, 컴파일 타임에 값이 검증되도록 해준다. 문서
기본 Enum 사용법
Java Enum은 관련된 상수들을 집합으로 표현하는 방식이다. 예를 들어 피자 주문 상태를 나타내는 Enum을 정의할 수 있다.
public enum PizzaStatus {
ORDERED,
READY,
DELIVERED;
}
열거 시에는 콤마(`,`), 끝날 시에는 세미콜론(`;`)
이와 같이 Enum으로 정의된 상수는 코드 가독성을 높이고, 잘못된 값이 전달되는 예외 사항을 방지할 수 있다. 또한 유용한 메서드들이 많이 제공된다.
커스텀 Enum 메서드
기본 Enum에 추가적인 메서드를 정의하여 더 풍부한 API로 확장할 수 있다. 예를 들어, 피자가 `READY` 상태일 때만 배달이 가능하도록 `isDeliverable` 메서드를 정의해보겠다.
public class Pizza {
private PizzaStatus status;
public enum PizzaStatus {
ORDERED,
READY,
DELIVERED;
}
public boolean isDeliverable() {
if (getStatus() == PizzaStatus.READY) {
return true;
}
return false;
}
// Methods that set and get the status variable.
}
Enum 비교에서 "==" 연산자 사용
Enum 타입은 JVM에서 단 하나의 인스턴스만 존재하기 떄문에 `==` 연산자를 사용하여 안전하게 비교할 수 있다. `equals` 메서드 대신 `==`을 사용하면 `NullPointerException`을 방지할 수 있고, 컴파일 시점에 타입 안전성도 제공한다.
if (testPizza.getStatus() == Pizza.PizzaStatus.DELIVERED) {
// 배달 완료 상태
}
Switch문에서 Enum 사용
Enum은 `switch`문에서 사용이 가능하여 코드 가독성을 높인다.
public int getDeliveryTimeInDays() {
switch (status) {
case ORDERED: return 5;
case READY: return 2;
case DELIVERED: return 0;
}
return 0;
}
필드, 메서드, 생성자가 포함된 Enum
Enum 내부에 필드, 메서드, 생성자를 정의하여 유연하게 사용할 수 있다. 각 피자 상태에 따라 다른 `timeToDelivery` 값을 설정하고, 이에 따른 상태별 메서드를 추가해보았다. 이전에 사용했던 `if`와 `switch`문을 제거할 수 있다.
public class Pizza {
private PizzaStatus status;
public enum PizzaStatus {
ORDERED (5){
@Override
public boolean isOrdered() {
return true;
}
},
READY (2){
@Override
public boolean isReady() {
return true;
}
},
DELIVERED (0){
@Override
public boolean isDelivered() {
return true;
}
};
private int timeToDelivery;
public boolean isOrdered() {return false;}
public boolean isReady() {return false;}
public boolean isDelivered(){return false;}
public int getTimeToDelivery() {
return timeToDelivery;
}
PizzaStatus (int timeToDelivery) {
this.timeToDelivery = timeToDelivery;
}
}
public boolean isDeliverable() {
return this.status.isReady();
}
public void printTimeToDeliver() {
System.out.println("Time to delivery is " +
this.getStatus().getTimeToDelivery());
}
// Methods that set and get the status variable.
}
테스트 코드
@Test
public void givenPizaOrder_whenReady_thenDeliverable() {
Pizza testPz = new Pizza();
testPz.setStatus(Pizza.PizzaStatus.READY);
assertTrue(testPz.isDeliverable());
}
EnumSet과 EnumMap 활용
EnumSet
`EnumSet`은 추상 클래스이며, `RegularEnumSet`, `JumboEnumSet`이라는 두 가지 구현을 갖고 있다. 이 중 어느 구현이 사용될지는 인스턴스 생성시 Enum의 상수 개수에 따라 결정된다.
따라서 Enum 상수 집합을 다루어야 할 때는 `EnumSet`을 사용하는 것이 좋다(부분 집합 생성, 추가, 삭제, `containsAll`, `removeAll`과 같은 대량 연산에 적합). 모든 상수를 순회만 하고 싶다면 `Enum.values()`를 사용하는 것이 좋다.
`EnumSet`을 사용하여 배달되지 않은 상태를 가진 피자만 필터링할 수 있다.
public class Pizza {
// 추가된 부분
private static EnumSet<PizzaStatus> undeliveredPizzaStatuses =
EnumSet.of(PizzaStatus.ORDERED, PizzaStatus.READY);
private PizzaStatus status;
public enum PizzaStatus {
...
}
public boolean isDeliverable() {
return this.status.isReady();
}
public void printTimeToDeliver() {
System.out.println("Time to delivery is " +
this.getStatus().getTimeToDelivery() + " days");
}
// 추가된 부분
public static List<Pizza> getAllUndeliveredPizzas(List<Pizza> input) {
return input.stream().filter(
(s) -> undeliveredPizzaStatuses.contains(s.getStatus()))
.collect(Collectors.toList());
}
public void deliver() {
if (isDeliverable()) {
PizzaDeliverySystemConfiguration.getInstance().getDeliveryStrategy()
.deliver(this);
this.setStatus(PizzaStatus.DELIVERED);
}
}
// Methods that set and get the status variable.
}
테스트 코드
@Test
public void givenPizaOrders_whenRetrievingUnDeliveredPzs_thenCorrectlyRetrieved() {
List<Pizza> pzList = new ArrayList<>();
Pizza pz1 = new Pizza();
pz1.setStatus(Pizza.PizzaStatus.DELIVERED);
Pizza pz2 = new Pizza();
pz2.setStatus(Pizza.PizzaStatus.ORDERED);
Pizza pz3 = new Pizza();
pz3.setStatus(Pizza.PizzaStatus.ORDERED);
Pizza pz4 = new Pizza();
pz4.setStatus(Pizza.PizzaStatus.READY);
pzList.add(pz1);
pzList.add(pz2);
pzList.add(pz3);
pzList.add(pz4);
List<Pizza> undeliveredPzs = Pizza.getAllUndeliveredPizzas(pzList);
assertTrue(undeliveredPzs.size() == 3);
}
EnumMap
`EnumMap`은 Enum 상수를 키로 사용하는 맵을 효율적으로 관리하는 클래스이다.
EnumMap<Pizza.PizzaStatus, Pizza> map;
아래는 피자 상태별로 피자 리스트를 그룹화하는 예제이다.
public static EnumMap<PizzaStatus, List<Pizza>> groupPizzaByStatus(List<Pizza> pizzas) {
return pizzas.stream()
.collect(Collectors.groupingBy(
Pizza::getStatus,
() -> new EnumMap<>(PizzaStatus.class),
Collectors.toList()
));
}
테스트 코드
@Test
public void givenPizaOrders_whenGroupByStatusCalled_thenCorrectlyGrouped() {
List<Pizza> pzList = new ArrayList<>();
Pizza pz1 = new Pizza();
pz1.setStatus(Pizza.PizzaStatus.DELIVERED);
Pizza pz2 = new Pizza();
pz2.setStatus(Pizza.PizzaStatus.ORDERED);
Pizza pz3 = new Pizza();
pz3.setStatus(Pizza.PizzaStatus.ORDERED);
Pizza pz4 = new Pizza();
pz4.setStatus(Pizza.PizzaStatus.READY);
pzList.add(pz1);
pzList.add(pz2);
pzList.add(pz3);
pzList.add(pz4);
EnumMap<Pizza.PizzaStatus, List<Pizza>> map = Pizza.groupPizzaByStatus(pzList);
assertTrue(map.get(Pizza.PizzaStatus.DELIVERED).size() == 1);
assertTrue(map.get(Pizza.PizzaStatus.ORDERED).size() == 2);
assertTrue(map.get(Pizza.PizzaStatus.READY).size() == 1);
}
디자인 패턴 구현하기
Singleton 패턴
Java의 `enum`은 Singleton 패턴을 간단하게 구현할 수 있다.
Enum 클래스는 내부적으로 `Serializable` 인터페이스를 구현하므로, JVM이 싱글톤을 보장해준다. 이는 기존의 싱글톤 구현 방식과 달리, 역질렬화 시에 새로운 인스턴스가 생성되지 않도록 따로 관리할 필요가 없음을 의미한다.
싱글톤 패턴을 구현하는 방법
public enum PizzaDeliverySystemConfiguration {
INSTANCE;
PizzaDeliverySystemConfiguration() {
// Initialization configuration which involves
// overriding defaults like delivery strategy
}
private PizzaDeliveryStrategy deliveryStrategy = PizzaDeliveryStrategy.NORMAL;
public static PizzaDeliverySystemConfiguration getInstance() {
return INSTANCE;
}
public PizzaDeliveryStrategy getDeliveryStrategy() {
return deliveryStrategy;
}
}
Strategy Pattern
전통적으로 strategy pattern은 여러 클래스가 구현하는 인터페이스를 통해 작성된다.
새로운 strategy을 추가하려면 새로운 구현 클래스를 추가해야 한다. 그러나 Enum을 사용하면 더욱 간단하게 이 패턴을 구현할 수 있으며, 새로운 구현ㅇ르 추가하는 것은 단순히 새로운 인스턴스에 구현을 정의하는 것으로 가능하다.
아래는 피자 배달 전략을 `EXPRESS`와 `NORMAL`로 나누고, 각 Enum에 구현을 추가해본다.
public enum PizzaDeliveryStrategy {
EXPRESS {
@Override
public void deliver(Pizza pz) {
System.out.println("Pizza will be delivered in express mode");
}
},
NORMAL {
@Override
public void deliver(Pizza pz) {
System.out.println("Pizza will be delivered in normal mode");
}
};
public abstract void deliver(Pizza pz);
}
이후 `Pizza` 클래스에 다음 메서드를 추가한다:
public void deliver() {
if (isDeliverable()) {
PizzaDeliverySystemConfiguration.getInstance().getDeliveryStrategy()
.deliver(this);
this.setStatus(PizzaStatus.DELIVERED);
}
}
테스트 코드
@Test
public void givenPizaOrder_whenDelivered_thenPizzaGetsDeliveredAndStatusChanges() {
Pizza pz = new Pizza();
pz.setStatus(Pizza.PizzaStatus.READY);
pz.deliver();
assertTrue(pz.getStatus() == Pizza.PizzaStatus.DELIVERED);
}
이렇게 Enum을 사용하면 싱글톤과 전략 패턴을 쉽게 구현할 수 있다.
JSON으로 Enum 표현하기
`Jackson` 라이브러리를 사용하면 Enum을 JSON 객체로 직렬화할 수 있다.
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum PizzaStatus {
ORDERED (5) {
@Override
public boolean isOrdered() {
return true;
}
},
READY (2) {
@Override
public boolean isReady() {
return true;
}
},
DELIVERED (0) {
@Override
public boolean isDelivered() {
return true;
}
};
private int timeToDelivery;
public boolean isOrdered() { return false; }
public boolean isReady() { return false; }
public boolean isDelivered() { return false; }
@JsonProperty("timeToDelivery")
public int getTimeToDelivery() {
return timeToDelivery;
}
private PizzaStatus(int timeToDelivery) {
this.timeToDelivery = timeToDelivery;
}
}
Pizza pz = new Pizza();
pz.setStatus(Pizza.PizzaStatus.READY);
System.out.println(Pizza.getJsonString(pz));
위 코드 실행 시 다음과 같은 피자의 상태에 대한 JSON표현이 생성된다.
{
"status" : {
"timeToDelivery" : 2,
"ready" : true,
"ordered" : false,
"delivered" : false
},
"deliverable" : true
}
Enum 타입의 Json 직렬화/역직렬화 방법에 대한 추가 정보는 이 문서를 참고할 수 있다.
참고
https://www.baeldung.com/a-guide-to-java-enums
'Java' 카테고리의 다른 글
[Java] static이란? (1) | 2024.11.08 |
---|---|
[AssertJ] Custom Exception (0) | 2024.10.31 |
[AssertJ] Exception Assertions (0) | 2024.10.31 |
객체 지향 프로그래밍(Object-Oriented Programming, OOP) (0) | 2024.10.26 |
[JUnit] @Parameterized Test (0) | 2024.10.26 |