Ръководство за Java Enums

1. Общ преглед

В тази статия ще видим какво представляват изброяванията на Java, какви проблеми те решават и как някои от дизайнерските модели могат да бъдат използвани на практика.

В ENUM Ключовата дума е въведена в Java 5. Това означава специален тип клас, който винаги разширява java.lang.Enum клас. За официалната документация за тяхното използване погледнете документацията.

Константите, дефинирани по този начин, правят кода по-четим, позволяват проверка по време на компилация, документират предварително списъка с приети стойности и избягват неочаквано поведение поради предаване на невалидни стойности.

Ето бърз и прост пример за изброяване, което определя състоянието на поръчка за пица; статусът на поръчката може да бъде ПОРЪЧАН , ГОТОВ или ДОСТАВЕН :

public enum PizzaStatus { ORDERED, READY, DELIVERED; }

Освен това те идват с много полезни методи, които иначе би трябвало да напишете сами, ако използвате традиционни публични статични крайни константи.

2. Персонализирани методи за изброяване

Добре, така че сега, когато имаме основно разбиране за това какво представляват изброяванията и как можете да ги използвате, нека вземем нашия предишен пример на следващото ниво, като дефинираме някои допълнителни методи на 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 с помощта на оператор “==”

Тъй като типовете enum гарантират, че в JVM съществува само един екземпляр от константите, можем безопасно да използваме оператора “==”, за да сравним две променливи, както се вижда в горния пример; освен това операторът “==” осигурява безопасност по време на компилация и време на изпълнение.

Нека първо разгледаме безопасността по време на изпълнение в следващия фрагмент, където операторът „==” се използва за сравняване на състояния и NullPointerException няма да бъде хвърлен, ако някоя от стойностите е нула . Обратно, NullPointerException ще бъде хвърлен, ако се използва методът equals:

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

Що се отнася до безопасността по време на компилиране , нека да разгледаме друг пример, при който се сравнява enum от различен тип, използвайки метода equals , за да е истина - тъй като стойностите на enum и метода getStatus по едно и също съвпадение са еднакви, но логично сравнението трябва да е невярно. Този проблем се избягва с помощта на оператора “==”.

Компилаторът ще маркира сравнението като грешка за несъвместимост:

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

4. Използване на типове Enum в изявления за превключване

Типовете Enum могат да се използват и в оператори за превключване :

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

5. Полета, методи и конструктори в Enums

Можете да дефинирате конструктори, методи и полета в типовете на преброяване, които го правят много мощен.

Нека се простират примера по-горе и изпълнение на прехода от един етап на пица на друг и да видим как можем да се отървете от , ако изявление и превключвател изявление използва преди:

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. EnumSet и EnumMap

6.1. EnumSet

В EnumSet е специализирана Set изпълнение предназначен да се използва с Enum видове.

Това е много ефективно и компактно представяне на определен набор от константи на Enum в сравнение с HashSet , поради използваното вътрешно представяне на битови вектори . И осигурява безопасна за типа алтернатива на традиционните „битови флагове“, базирани на int , позволявайки ни да напишем кратък код, който е по-четим и поддържаем.

В EnumSet е абстрактен клас, който има две приложения, наречени RegularEnumSet и JumboEnumSet , един от които се избира в зависимост от броя на константи в Enum по време на инстанция.

Затова винаги е добра идея да използваме този набор винаги, когато искаме да работим с колекция от константи на enum в повечето от сценариите (като подмножество, добавяне, премахване и за групови операции като containsAll и removeAll ) и да използваме 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. } 

Изпълнението на следния тест демонстрира мощта на изпълнението на EnumSet на интерфейса Set :

@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 е специализирана реализация на Map, предназначена да се използва с 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; } 
      
     
    

Изпълнението на следния тест показа силата на реализацията на EnumMap на интерфейса на Map :

@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. Внедрете дизайнерски модели, използвайки Enums

7.1. Единичен модел

Обикновено внедряването на клас, използващ модела Singleton, е доста нетривиално. Enums предоставят лесен и бърз начин за внедряване на единични файлове.

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 enum, от основите на езика до по-напреднали и интересни случаи на реално използване.

Кодови фрагменти от тази статия могат да бъдат намерени в хранилището на Github.