Een gids voor Java-enums

1. Overzicht

In dit artikel zullen we zien wat Java-enums zijn, welke problemen ze oplossen en hoe sommige van de ontwerppatronen ze in de praktijk kunnen gebruiken.

De opsomming trefwoord is geïntroduceerd in Java 5. Het geeft een speciaal type klasse aan dat altijd de extensie java.lang.Enum klasse. Raadpleeg de documentatie voor de officiële documentatie over hun gebruik.

Constanten die op deze manier zijn gedefinieerd, maken de code leesbaarder, maken controle tijdens compileren mogelijk, documenteren de lijst met geaccepteerde waarden vooraf en voorkomen onverwacht gedrag als gevolg van het invoeren van ongeldige waarden.

Hier is een snel en eenvoudig voorbeeld van een opsomming die de status van een bestelling voor een pizza definieert; de orderstatus kan zijn BESTELD, KLAAR of GELEVERD:

openbare opsomming PizzaStatus {BESTELD, KLAAR, GELEVERD; }

Bovendien bevatten ze veel handige methoden, die u anders zelf zou moeten schrijven als u traditionele openbare statische eindconstanten zou gebruiken.

2. Aangepaste Enum-methoden

OK, dus nu we een basiskennis hebben van wat enums zijn en hoe u ze kunt gebruiken, laten we ons vorige voorbeeld naar een hoger niveau tillen door enkele extra API-methoden op de enum te definiëren:

openbare klasse Pizza {privé PizzaStatus-status; openbare opsomming PizzaStatus {BESTELD, KLAAR, GELEVERD; } openbare boolean isDeliverable () {if (getStatus () == PizzaStatus.READY) {return true; } return false; } // Methoden die de statusvariabele instellen en ophalen. } 

3. Enum-typen vergelijken met de "==" operator

Aangezien opsommingstypen ervoor zorgen dat er slechts één instantie van de constanten in de JVM bestaat, kunnen we veilig de operator "==" gebruiken om twee variabelen te vergelijken, zoals in het bovenstaande voorbeeld; bovendien biedt de "==" operator compilatietijd en runtime-veiligheid.

Laten we eerst eens kijken bij run-time veiligheid in het volgende fragment waar de "==" operator wordt gebruikt om statussen te vergelijken en een NullPointerException zal niet worden gegooid als een van beide waarden is nul. Omgekeerd is de an NullPointerException zou worden gegooid als de equals-methode werd gebruikt:

if (testPz.getStatus (). is gelijk aan (Pizza.PizzaStatus.DELIVERED)); if (testPz.getStatus () == Pizza.PizzaStatus.DELIVERED); 

Wat betreft compileer tijdveiligheid, laten we eens kijken naar een ander voorbeeld waarin een enum van een ander type wordt vergeleken met de is gelijk aan methode wordt vastgesteld als waar - omdat de waarden van de enum en de getStatus methode toevallig hetzelfde zijn, maar logischerwijs zou de vergelijking onjuist moeten zijn. Dit probleem wordt vermeden door de operator "==" te gebruiken.

De compiler markeert de vergelijking als een incompatibiliteitsfout:

if (testPz.getStatus (). is gelijk aan (TestColor.GREEN)); if (testPz.getStatus () == TestColor.GREEN); 

4. Opsommingstypen gebruiken in schakelverklaringen

Enum-typen kunnen worden gebruikt in een schakelaar verklaringen ook:

openbare int getDeliveryTimeInDays () {switch (status) {geval BESTELD: terugkeer 5; case READY: return 2; case GELEVERD: retourneer 0; } retourneren 0; }

5. Velden, methoden en constructors in Enums

U kunt constructors, methoden en velden binnen opsommingstypen definiëren die het zeer krachtig maken.

Laten we het bovenstaande voorbeeld uitbreiden en de overgang van de ene fase van een pizza naar de andere implementeren en kijken hoe we de als verklaring en schakelaar verklaring eerder gebruikt:

openbare klasse Pizza {privé PizzaStatus-status; openbare opsomming PizzaStatus {ORDERED (5) {@Override openbare boolean isOrdered () {return true; }}, READY (2) {@Override public boolean isReady () {return true; }}, GELEVERD (0) {@Override openbare boolean isDelivered () {terugkeer waar; }}; privé int timeToDelivery; openbare boolean isOrdered () {terugkeer onwaar;} openbare boolean isReady () {terugkeer onwaar;} openbare boolean isDelivered () {terugkeer onwaar;} openbaar int getTimeToDelivery () {terugkeer timeToDelivery; } PizzaStatus (int timeToDelivery) {this.timeToDelivery = timeToDelivery; }} openbare boolean isDeliverable () {retourneer this.status.isReady (); } public void printTimeToDeliver () {System.out.println ("Tijd tot levering is" + this.getStatus (). getTimeToDelivery ()); } // Methoden die de statusvariabele instellen en ophalen. } 

Het onderstaande testfragment laat zien hoe dit werkt:

@Test openbare ongeldig gegevenPizaOrder_whenReady_thenDeliverable () {Pizza testPz = nieuwe pizza (); testPz.setStatus (Pizza.PizzaStatus.READY); assertTrue (testPz.isDeliverable ()); }

6. EnumSet en EnumMap

6.1. EnumSet

De EnumSet is een gespecialiseerd Set implementatie bedoeld om te worden gebruikt met Enum types.

Het is een zeer efficiënte en compacte weergave van een bepaald Set van Enum constanten in vergelijking met een HashSet, vanwege de interne Bit Vector Vertegenwoordiging dat wordt gebruikt. En het biedt een type-veilig alternatief voor traditioneel int-gebaseerde "bitvlaggen", waardoor we beknopte code kunnen schrijven die beter leesbaar en onderhoudbaar is.

De EnumSet is een abstracte klasse die twee implementaties heeft genaamd RegularEnumSet en JumboEnumSet, waarvan er één wordt gekozen afhankelijk van het aantal constanten in de opsomming op het moment van instantiatie.

Daarom is het altijd een goed idee om deze set te gebruiken wanneer we in de meeste scenario's met een verzameling opsommingsconstanten willen werken (zoals subsets, toevoegen, verwijderen en voor bulkbewerkingen zoals bevatAll en Verwijder alles) en gebruiken Enum. Waarden () als je gewoon alle mogelijke constanten wilt herhalen.

In het onderstaande codefragment kunt u zien hoe EnumSet wordt gebruikt om een ​​subset van constanten en het gebruik ervan te maken:

openbare klasse Pizza {privé statisch EnumSet undeliveredPizzaStatuses = EnumSet.of (PizzaStatus.ORDERED, PizzaStatus.READY); privé PizzaStatus-status; openbare opsomming PizzaStatus {...} openbare boolean isDeliverable () {return this.status.isReady (); } public void printTimeToDeliver () {System.out.println ("Tijd tot levering is" + this.getStatus (). getTimeToDelivery () + "dagen"); } openbare statische lijst getAllUndeliveredPizzas (lijstinvoer) {return input.stream (). filter ((s) -> undeliveredPizzaStatuses.contains (s.getStatus ())) .collect (Collectors.toList ()); } public void deliver () {if (isDeliverable ()) {PizzaDeliverySystemConfiguration.getInstance (). getDeliveryStrategy () .deliver (dit); this.setStatus (PizzaStatus.DELIVERED); }} // Methoden die de statusvariabele instellen en ophalen. } 

Het uitvoeren van de volgende test toonde de kracht van het EnumSet implementatie van de Set koppel:

@Test openbare ongeldige gegevenPizaOrders_whenRetrievingUnDeliveredPzs_thenCorrectlyRetrieved () {List pzList = nieuwe ArrayList (); Pizza pz1 = nieuwe pizza (); pz1.setStatus (Pizza.PizzaStatus.DELIVERED); Pizza pz2 = nieuwe pizza (); pz2.setStatus (Pizza.PizzaStatus.ORDERED); Pizza pz3 = nieuwe pizza (); pz3.setStatus (Pizza.PizzaStatus.ORDERED); Pizza pz4 = nieuwe pizza (); pz4.setStatus (Pizza.PizzaStatus.READY); pzList.add (pz1); pzList.add (pz2); pzList.add (pz3); pzList.add (pz4); Lijst undeliveredPzs = Pizza.getAllUndeliveredPizzas (pzList); assertTrue (undeliveredPzs.size () == 3); }

6.2. EnumMap

EnumMap is een gespecialiseerd Kaart implementatie bedoeld om te worden gebruikt met enum-constanten als sleutels. Het is een efficiënte en compacte implementatie in vergelijking met zijn tegenhanger Hash kaart en wordt intern weergegeven als een array:

EnumMap kaart; 

Laten we snel een echt voorbeeld bekijken dat laat zien hoe het in de praktijk kan worden gebruikt:

openbare statische EnumMap groupPizzaByStatus (Lijst pizzalijst) {EnumMap pzByStatus = nieuwe EnumMap(PizzaStatus.class); voor (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); }} retourneer pzByStatus; } 

Het uitvoeren van de volgende test toonde de kracht van het EnumMap implementatie van de Kaart koppel:

@Test openbare ongeldig gegevenPizaOrders_whenGroupByStatusCalled_thenCorrectlyGrouped () {Lijst pzList = nieuwe ArrayList (); Pizza pz1 = nieuwe pizza (); pz1.setStatus (Pizza.PizzaStatus.DELIVERED); Pizza pz2 = nieuwe pizza (); pz2.setStatus (Pizza.PizzaStatus.ORDERED); Pizza pz3 = nieuwe pizza (); pz3.setStatus (Pizza.PizzaStatus.ORDERED); Pizza pz4 = nieuwe 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. Implementeer ontwerppatronen met behulp van Enums

7.1. Singleton-patroon

Normaal gesproken is het implementeren van een klasse met behulp van het Singleton-patroon niet triviaal. Enums bieden een gemakkelijke en snelle manier om singletons te implementeren.

Bovendien, aangezien de enum-klasse de Serialiseerbaar interface onder de motorkap, is de klasse gegarandeerd een singleton door de JVM, wat in tegenstelling tot de conventionele implementatie waarbij we ervoor moeten zorgen dat er geen nieuwe instanties worden gemaakt tijdens deserialisatie.

In het onderstaande codefragment zien we hoe we singleton-patroon kunnen implementeren:

openbare opsomming PizzaDeliverySystemConfiguration {INSTANCE; PizzaDeliverySystemConfiguration () {// Initialisatieconfiguratie waarbij // standaardwaarden zoals bezorgstrategie worden overschreven} privé PizzaDeliveryStrategy deliveryStrategy = PizzaDeliveryStrategy.NORMAL; openbare statische PizzaDeliverySystemConfiguration getInstance () {return INSTANCE; } openbare PizzaDeliveryStrategy getDeliveryStrategy () {return deliveryStrategy; }}

7.2. Strategiepatroon

Conventioneel wordt het Strategiepatroon geschreven door een interface te hebben die wordt geïmplementeerd door verschillende klassen.

Het toevoegen van een nieuwe strategie betekende het toevoegen van een nieuwe implementatieklasse. Met enums wordt dit bereikt met minder moeite, het toevoegen van een nieuwe implementatie betekent het definiëren van slechts een ander exemplaar met enige implementatie.

Het onderstaande codefragment laat zien hoe u het Strategiepatroon implementeert:

openbare opsomming PizzaDeliveryStrategy {EXPRESS {@Override public void delivery (Pizza pz) {System.out.println ("Pizza wordt bezorgd in express-modus"); }}, NORMAAL {@Override openbare nietige bezorging (Pizza pz) {System.out.println ("Pizza wordt bezorgd in normale modus"); }}; openbare abstracte leegte bezorgen (Pizza pz); }

Voeg de volgende methode toe aan het Pizza klasse:

openbare ongeldige levering () {if (isDeliverable ()) {PizzaDeliverySystemConfiguration.getInstance (). getDeliveryStrategy () .levering (dit); this.setStatus (PizzaStatus.DELIVERED); }}
@Test openbare leegte gegevenPizaOrder_whenDelivered_thenPizzaGetsDeliveredAndStatusChanges () {Pizza pz = nieuwe pizza (); pz.setStatus (Pizza.PizzaStatus.READY); pz.deliver (); assertTrue (pz.getStatus () == Pizza.PizzaStatus.DELIVERED); }

8. Java 8 en Enums

De Pizza class kan worden herschreven in Java 8, en u kunt zien hoe de methoden getAllUndeliveredPizzas () en groupPizzaByStatus () zo beknopt worden met het gebruik van lambda's en de Stroom API's:

openbare statische lijst getAllUndeliveredPizzas (lijstinvoer) {return input.stream (). filter ((s) ->! afgeleverdPizzaStatuses.contains (s.getStatus ())) .collect (Collectors.toList ()); } 
openbare statische EnumMap groupPizzaByStatus (Lijst pzList) {EnumMap map = pzList.stream (). collect (Collectors.groupingBy (Pizza :: getStatus, () -> nieuwe EnumMap (PizzaStatus.class), Collectors.toList ())); kaart teruggeven; }

9. JSON-weergave van Enum

Met behulp van Jackson-bibliotheken is het mogelijk om een ​​JSON-weergave van opsommingstypen te hebben alsof het POJO's zijn. Het onderstaande codefragment toont de Jackson-annotaties die voor hetzelfde kunnen worden gebruikt:

@JsonFormat (vorm = JsonFormat.Shape.OBJECT) openbare opsomming PizzaStatus {ORDERED (5) {@Override openbare boolean isOrdered () {return true; }}, READY (2) {@Override public boolean isReady () {return true; }}, GELEVERD (0) {@Override openbare boolean isDelivered () {terugkeer waar; }}; privé int timeToDelivery; public boolean isOrdered () {return false;} public boolean isReady () {return false;} public boolean isDelivered () {return false;} @JsonProperty ("timeToDelivery") public int getTimeToDelivery () {return timeToDelivery; } privé PizzaStatus (int timeToDelivery) {this.timeToDelivery = timeToDelivery; }} 

We kunnen de Pizza en PizzaStatus als volgt gebruiken:

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

om de volgende JSON-weergave van het Pizzas status:

{"status": {"timeToDelivery": 2, "ready": true, "besteld": false, "geleverd": false}, "deliverable": true}

Voor meer informatie over JSON-serialisering / deserialisering (inclusief aanpassing) van opsommingstypen, raadpleegt u de Jackson - Enums serialiseren als JSON-objecten.

10. Conclusie

In dit artikel hebben we de Java-opsomming onderzocht, van de basisprincipes van de taal tot meer geavanceerde en interessante praktijkvoorbeelden.

Codefragmenten uit dit artikel zijn te vinden in de Github-repository.