자바 열거 형 가이드

1. 개요

이 기사에서는 Java 열거 형이 무엇인지, 어떤 문제를 해결하는지, 실제로 사용할 수있는 일부 디자인 패턴을 살펴 보겠습니다.

열거 키워드는 자바 5에 도입 그것은 항상 확장 클래스의 특별한 유형이다 java.lang.Enum의 클래스를. 사용법에 대한 공식 문서는 문서를 참조하십시오.

이러한 방식으로 정의 된 상수는 코드를 더 읽기 쉽게 만들고, 컴파일 타임 검사를 허용하고, 허용 된 값 목록을 미리 문서화하고 잘못된 값이 전달되어 예기치 않은 동작을 방지합니다.

다음은 피자 주문 상태를 정의하는 열거 형의 빠르고 간단한 예입니다. 주문 상태는 ORDERED , READY 또는 DELIVERED 일 수 있습니다 .

public enum PizzaStatus { ORDERED, READY, DELIVERED; }

또한 기존의 공용 정적 최종 상수를 사용하는 경우 직접 작성해야하는 유용한 메서드가 많이 제공됩니다.

2. 사용자 지정 Enum 메서드

자, 이제 우리는 열거 형이 무엇이며 어떻게 사용할 수 있는지에 대한 기본적인 이해를 얻었으므로 열거 형에 몇 가지 추가 API 메서드를 정의하여 이전 예제를 한 단계 더 발전시켜 보겠습니다.

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. } 

3.“==”연산자를 사용하여 열거 형 비교

enum 유형은 JVM에 상수 인스턴스가 하나만 존재하도록 보장하므로 위의 예에서 볼 수 있듯이 "=="연산자를 사용하여 두 변수를 비교할 수 있습니다. 또한 "=="연산자는 컴파일 타임 및 런타임 안전성을 제공합니다.

먼저 "=="연산자가 상태를 비교하는 데 사용되며 값이 null 인 경우 NullPointerException 이 발생하지 않는 다음 스 니펫 에서 런타임 안전성 을 살펴 보겠습니다 . 반대로 equals 메소드가 사용되면 NullPointerException 이 발생합니다.

if(testPz.getStatus().equals(Pizza.PizzaStatus.DELIVERED)); if(testPz.getStatus() == Pizza.PizzaStatus.DELIVERED); 

컴파일 시간 안전성에 관해서 는 equals 메소드를 사용하여 다른 유형의 enum을 비교하는 또 다른 예를 살펴 보겠습니다 . enum과 getStatus 메소드 의 값이 동시에 동일하지만 논리적으로는 비교는 거짓이어야합니다. 이 문제는 "=="연산자를 사용하여 방지됩니다.

컴파일러는 비교를 비 호환성 오류로 표시합니다.

if(testPz.getStatus().equals(TestColor.GREEN)); if(testPz.getStatus() == TestColor.GREEN); 

4. Switch 문에서 열거 형 사용

열거 형 유형은 switch 문에서도 사용할 수 있습니다 .

public int getDeliveryTimeInDays() { switch (status) { case ORDERED: return 5; case READY: return 2; case DELIVERED: return 0; } return 0; }

5. 열거 형의 필드, 메서드 및 생성자

생성자, 메서드 및 필드를 매우 강력하게 만드는 열거 형 내부에 정의 할 수 있습니다.

위의 예제를 확장하고 피자의 한 단계에서 다른 단계로의 전환을 구현하고 이전에 사용 된 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()); }

6. EnumSetEnumMap

6.1. EnumSet

각각 EnumSet은 전문 기업입니다 세트 와 함께 사용하기위한 것 구현 열거 유형.

사용되는 내부 비트 벡터 표현 으로 인해 HashSet 과 비교할 때 특정 Enum 상수 세트 의 매우 효율적이고 간결한 표현입니다 . 또한 기존의 int 기반 "비트 플래그"에 대한 형식이 안전한 대안을 제공하여 보다 읽기 쉽고 유지 관리가 쉬운 간결한 코드를 작성할 수 있습니다.

각각 EnumSet은 이라는 두 가지 구현 갖는 추상 클래스 RegularEnumSetJumboEnumSet 인스턴스화시에 열거 상수의 수에 따라 선택되는 어느 하나를.

따라서 대부분의 시나리오 (부 세트 화, 추가, 제거 및 containsAllremoveAll 과 같은 대량 작업)에서 열거 형 상수 모음을 사용하고 싶을 때마다이 집합 을 사용하고 Enum.values ​​(를 사용하는 것이 좋습니다. ) 가능한 모든 상수를 반복하려는 경우.

아래 코드 스 니펫에서 EnumSet 을 사용하여 상수 하위 집합과 그 사용법을 만드는 방법을 확인할 수 있습니다.

public class Pizza { private static EnumSet 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 getAllUndeliveredPizzas(List 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. } 

다음 테스트를 실행하면 Set 인터페이스 의 EnumSet 구현의 힘이 입증되었습니다 .

@Test public void givenPizaOrders_whenRetrievingUnDeliveredPzs_thenCorrectlyRetrieved() { List 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 undeliveredPzs = Pizza.getAllUndeliveredPizzas(pzList); assertTrue(undeliveredPzs.size() == 3); }

6.2. EnumMap

EnumMap 은 enum 상수를 키로 사용하기 위한 특수 구현입니다. 대응하는 HashMap에 비해 효율적이고 간결한 구현 이며 내부적으로 배열로 표시됩니다.

EnumMap map; 

실제로 사용하는 방법을 보여주는 실제 예를 간단히 살펴 보겠습니다.

public static EnumMap
    
      groupPizzaByStatus(List pizzaList) { EnumMap
     
       pzByStatus = new EnumMap
      
       (PizzaStatus.class); for (Pizza pz : pizzaList) { PizzaStatus status = pz.getStatus(); if (pzByStatus.containsKey(status)) { pzByStatus.get(status).add(pz); } else { List newPzList = new ArrayList(); newPzList.add(pz); pzByStatus.put(status, newPzList); } } return pzByStatus; } 
      
     
    

다음 테스트를 실행하여 Map 인터페이스 의 EnumMap 구현의 힘을 입증했습니다 .

@Test public void givenPizaOrders_whenGroupByStatusCalled_thenCorrectlyGrouped() { List 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
    
      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); }
    

7. 열거 형을 사용하여 디자인 패턴 구현

7.1. 싱글 톤 패턴

일반적으로 Singleton 패턴을 사용하여 클래스를 구현하는 것은 매우 간단합니다. 열거 형은 싱글 톤을 구현하는 쉽고 빠른 방법을 제공합니다.

In addition to that, since the enum class implements the Serializable interface under the hood, the class is guaranteed to be a singleton by the JVM, which unlike the conventional implementation where we have to ensure that no new instances are created during deserialization.

In the code snippet below, we see how we can implement singleton pattern:

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; } }

7.2. Strategy Pattern

Conventionally the Strategy pattern is written by having an interface that is implemented by different classes.

Adding a new strategy meant adding a new implementation class. With enums, this is achieved with less effort, adding a new implementation means defining just another instance with some implementation.

The code snippet below shows how to implement the Strategy pattern:

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); }

Add the following method to the Pizza class:

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); }

8. Java 8 and Enums

The Pizza class can be rewritten in Java 8, and you can see how the methods getAllUndeliveredPizzas() and groupPizzaByStatus() become so concise with the use of lambdas and the Stream APIs:

public static List getAllUndeliveredPizzas(List input) { return input.stream().filter( (s) -> !deliveredPizzaStatuses.contains(s.getStatus())) .collect(Collectors.toList()); } 
public static EnumMap
    
      groupPizzaByStatus(List pzList) { EnumMap
     
       map = pzList.stream().collect( Collectors.groupingBy(Pizza::getStatus, () -> new EnumMap(PizzaStatus.class), Collectors.toList())); return map; }
     
    

9. JSON Representation of Enum

Using Jackson libraries, it is possible to have a JSON representation of enum types as if they are POJOs. The code snippet below shows the Jackson annotations that can be used for the same:

@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; } } 

We can use the Pizza and PizzaStatus as follows:

Pizza pz = new Pizza(); pz.setStatus(Pizza.PizzaStatus.READY); System.out.println(Pizza.getJsonString(pz)); 

to generate the following JSON representation of the Pizzas status:

{ "status" : { "timeToDelivery" : 2, "ready" : true, "ordered" : false, "delivered" : false }, "deliverable" : true }

For more information on JSON serializing/deserializing (including customization) of enum types refer to the Jackson – Serialize Enums as JSON Objects.

10. Conclusion

이 기사에서 우리는 언어 기초부터 더 발전되고 흥미로운 실제 사용 사례에 이르기까지 Java 열거 형을 탐색했습니다.

이 기사의 코드 조각은 Github 저장소에서 찾을 수 있습니다.