En guide til Java Enums

1. Oversigt

I denne artikel vil vi se, hvad Java-enums er, hvilke problemer de løser, og hvordan nogle af designmønstrene de kan bruges i praksis.

Det enum nøgleord blev introduceret i Java 5. Det angiver en særlig type klasse, der altid udvider java.lang.Enum klasse. For den officielle dokumentation om deres brug, se dokumentationen.

Konstanter defineret på denne måde gør koden mere læselig, tillader kontrol af kompileringstid, dokumenterer forud for listen over accepterede værdier og undgår uventet opførsel på grund af ugyldige værdier, der sendes ind.

Her er et hurtigt og simpelt eksempel på en enum, der definerer status for en ordre på en pizza; ordrestatus kan være BESTILT, PARAT eller LEVERET:

offentlig enum PizzaStatus {BESTILT, KLAR, LEVERET; }

Derudover kommer de med mange nyttige metoder, som du ellers skulle skrive selv, hvis du brugte traditionelle offentlige statiske endelige konstanter.

2. Brugerdefinerede Enum-metoder

OK, så nu hvor vi har en grundlæggende forståelse af, hvad enums er, og hvordan du kan bruge dem, lad os tage vores tidligere eksempel til det næste niveau ved at definere nogle ekstra API-metoder på enum:

offentlig klasse Pizza {privat PizzaStatus-status; offentlig enum PizzaStatus {BESTILT, KLAR, LEVERET; } public boolean isDeliverable () {if (getStatus () == PizzaStatus.READY) {return true; } returner falsk; } // Metoder, der indstiller og henter statusvariablen. } 

3. Sammenligning af Enum-typer ved hjælp af “==” operatør

Da enumtyper sikrer, at der kun findes en forekomst af konstanterne i JVM, kan vi med sikkerhed bruge “==” -operatøren til at sammenligne to variabler som vist i eksemplet ovenfor; desuden giver operatøren “==” kompileringstid og kørselstidssikkerhed.

Lad os først kigge ved kørselstidssikkerhed i det følgende uddrag, hvor operatoren “==” bruges til at sammenligne status og a NullPointerException vil ikke blive kastet, hvis en af ​​værdierne er nul. Omvendt er an NullPointerException ville blive kastet, hvis ligemetoden blev brugt:

hvis (testPz.getStatus (). er lig med (Pizza.PizzaStatus.DELIVERED)); hvis (testPz.getStatus () == Pizza.PizzaStatus.DELIVERED); 

Som for kompilere tidssikkerhed, lad os se på et andet eksempel, hvor et enum af en anden type sammenlignes ved hjælp af lige med metoden er bestemt til at være sand - fordi værdierne for enum og getStatus metode er tilfældigt den samme, men logisk set skal sammenligningen være falsk. Dette problem undgås ved at bruge operatoren “==”.

Compileren markerer sammenligningen som en inkompatibilitetsfejl:

hvis (testPz.getStatus (). er lig med (TestColor.GREEN)); hvis (testPz.getStatus () == TestColor.GREEN); 

4. Brug af enumtyper i switch-erklæringer

Enumtyper kan bruges i en kontakt udsagn også:

public int getDeliveryTimeInDays () {switch (status) {case ORDERED: return 5; sag KLAR: retur 2; sag LEVERET: retur 0; } returner 0; }

5. Felter, metoder og konstruktører i Enums

Du kan definere konstruktører, metoder og felter inden for enumtyper, der gør det meget kraftigt.

Lad os udvide eksemplet ovenfor og implementere overgangen fra et trin i en pizza til et andet og se, hvordan vi kan slippe af med hvis erklæring og kontakt erklæring brugt før:

offentlig klasse Pizza {privat PizzaStatus-status; offentlig enum PizzaStatus {ORDERED (5) {@Override public boolean isOrdered () {return true; }}, KLAR (2) {@Override public boolean isReady () {return true; }}, LEVERET (0) {@Override public boolean isDelivered () {return true; }}; privat 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 () {returner this.status.isReady (); } offentlig ugyldig printTimeToDeliver () {System.out.println ("Tid til levering er" + this.getStatus (). getTimeToDelivery ()); } // Metoder, der indstiller og henter statusvariablen. } 

Testuddraget nedenfor viser, hvordan dette fungerer:

@Test offentlig ugyldighed givenPizaOrder_whenReady_thenDeliverable () {Pizza testPz = new Pizza (); testPz.setStatus (Pizza.PizzaStatus.READY); assertTrue (testPz.isDeliverable ()); }

6. EnumSet og EnumMap

6.1. EnumSet

Det EnumSet er specialiseret Sæt implementering beregnet til at blive brugt med Enum typer.

Det er en meget effektiv og kompakt gengivelse af et bestemt Sæt af Enum konstanter sammenlignet med a HashSetpå grund af det indre Bit vektorrepræsentation der bruges. Og det giver et typesikkert alternativ til traditionel int-baserede “bitflag”, der giver os mulighed for at skrive kortfattet kode, der er mere læselig og vedligeholdelig.

Det EnumSet er en abstrakt klasse, der har to implementeringer kaldet RegularEnumSet og JumboEnumSethvoraf den ene vælges afhængigt af antallet af konstanter i enum på tidspunktet for instantiering.

Derfor er det altid en god ide at bruge dette sæt, når vi vil arbejde med en samling af enumkonstanter i de fleste scenarier (som underindstilling, tilføjelse, fjernelse og til bulkoperationer som f.eks. indeholderAlle og Fjern alt) og brug Enum. Værdier () hvis du bare vil gentage alle mulige konstanter.

I kodestykket nedenfor kan du se hvordan EnumSet bruges til at oprette en delmængde af konstanter og dens anvendelse:

offentlig klasse Pizza {privat statisk EnumSet undeliveredPizzaStatuses = EnumSet.of (PizzaStatus.ORDERED, PizzaStatus.READY); privat PizzaStatus-status; public enum PizzaStatus {...} public boolean isDeliverable () {returner this.status.isReady (); } offentlig ugyldig printTimeToDeliver () {System.out.println ("Leveringstid er" + this.getStatus (). getTimeToDelivery () + "dage"); } offentlig statisk liste 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); }} // Metoder, der indstiller og henter statusvariablen. } 

Udførelse af følgende test demonstrerede styrken af EnumSet gennemførelse af Sæt grænseflade:

@Test offentlig ugyldighed givenPizaOrders_whenRetrievingUnDeliveredPzs_thenCorrectlyRetrieved () {List pzList = new ArrayList (); Pizza pz1 = ny Pizza (); pz1.setStatus (Pizza.PizzaStatus.DELIVERED); Pizza pz2 = ny Pizza (); pz2.setStatus (Pizza.PizzaStatus.ORDRERET); Pizza pz3 = ny Pizza (); pz3.setStatus (Pizza.PizzaStatus.ORDRERET); Pizza pz4 = ny Pizza (); pz4.setStatus (Pizza.PizzaStatus.READY); pzList.add (pz1); pzList.add (pz2); pzList.add (pz3); pzList.add (pz4); Liste undeliveredPzs = Pizza.getAllUndeliveredPizzas (pzList); assertTrue (undeliveredPzs.size () == 3); }

6.2. EnumMap

EnumMap er specialiseret Kort implementering beregnet til at blive brugt med enum-konstanter som nøgler. Det er en effektiv og kompakt implementering i forhold til dets modstykke HashMap og er internt repræsenteret som en matrix:

EnumMap-kort; 

Lad os se hurtigt på et rigtigt eksempel, der viser, hvordan det kan bruges i praksis:

offentlig statisk EnumMap groupPizzaByStatus (Liste pizzaList) {EnumMap pzByStatus = nyt EnumMap(PizzaStatus.klasse); for (Pizza pz: pizzaList) {PizzaStatus status = pz.getStatus (); hvis (pzByStatus.containsKey (status)) {pzByStatus.get (status) .add (pz); } ellers {List newPzList = new ArrayList (); newPzList.add (pz); pzByStatus.put (status, newPzList); }} returner pzByStatus; } 

Udførelse af følgende test demonstrerede styrken af EnumMap gennemførelse af Kort grænseflade:

@Test offentlig ugyldighed givenPizaOrders_whenGroupByStatusCalled_thenCorrectlyGrouped () {List pzList = new ArrayList (); Pizza pz1 = ny Pizza (); pz1.setStatus (Pizza.PizzaStatus.DELIVERED); Pizza pz2 = ny Pizza (); pz2.setStatus (Pizza.PizzaStatus.ORDRERET); Pizza pz3 = ny Pizza (); pz3.setStatus (Pizza.PizzaStatus.ORDRERET); Pizza pz4 = ny Pizza (); pz4.setStatus (Pizza.PizzaStatus.READY); pzList.add (pz1); pzList.add (pz2); pzList.add (pz3); pzList.add (pz4); EnumMap kort = Pizza.groupPizzaByStatus (pzList); assertTrue (map.get (Pizza.PizzaStatus.DELIVERED) .størrelse () == 1); assertTrue (map.get (Pizza.PizzaStatus.ORDERED) .størrelse () == 2); assertTrue (map.get (Pizza.PizzaStatus.READY) .størrelse () == 1); }

7. Implementere designmønstre ved hjælp af Enums

7.1. Singleton mønster

Normalt er implementering af en klasse ved hjælp af Singleton-mønsteret ret ikke-trivielt. Enums giver en nem og hurtig måde at implementere singletoner på.

Derudover, da enum-klassen implementerer Serialiserbar interface under emhætten, er klassen garanteret en singleton af JVM, som i modsætning til den konventionelle implementering, hvor vi skal sikre, at der ikke oprettes nye forekomster under deserialisering.

I nedenstående kodestykke ser vi, hvordan vi kan implementere singleton-mønster:

offentlig enum PizzaDeliverySystemConfiguration {INSTANCE; PizzaDeliverySystemConfiguration () {// Initialiseringskonfiguration, der involverer // overordnede standardindstillinger som leveringsstrategi} privat PizzaDeliveryStrategy deliveryStrategy = PizzaDeliveryStrategy.NORMAL; offentlig statisk PizzaDeliverySystemConfiguration getInstance () {return INSTANCE; } offentlig PizzaDeliveryStrategy getDeliveryStrategy () {retur leveringStrategy; }}

7.2. Strategimønster

Konventionelt er strategimønsteret skrevet ved at have en grænseflade, der implementeres af forskellige klasser.

Tilføjelse af en ny strategi betød at tilføje en ny implementeringsklasse. Med enums opnås dette med mindre indsats. Tilføjelse af en ny implementering betyder, at man bare definerer endnu en instans med en vis implementering.

Kodestykket nedenfor viser, hvordan strategimønsteret implementeres:

offentlig enum PizzaDeliveryStrategy {EXPRESS {@Override public void deliver (Pizza pz) {System.out.println ("Pizza leveres i ekspressilstand"); }}, NORMAL {@Override public void deliver (Pizza pz) {System.out.println ("Pizza leveres i normal tilstand"); }}; offentlig abstrakt ugyldig levering (Pizza pz); }

Føj følgende metode til Pizza klasse:

public void deliver () {if (isDeliverable ()) {PizzaDeliverySystemConfiguration.getInstance (). getDeliveryStrategy () .deliver (this); this.setStatus (PizzaStatus.DELIVERED); }}
@Test offentlig ugyldighed givenPizaOrder_whenDelivered_thenPizzaGetsDeliveredAndStatusChanges () {Pizza pz = new Pizza (); pz.setStatus (Pizza.PizzaStatus.READY); pz.leverer (); assertTrue (pz.getStatus () == Pizza.PizzaStatus.DELIVERED); }

8. Java 8 og Enums

Det Pizza klasse kan omskrives i Java 8, og du kan se, hvordan metoderne getAllUndeliveredPizzas () og gruppePizzaByStatus () blive så kortfattet med brugen af ​​lambdas og Strøm API'er:

offentlig statisk liste getAllUndeliveredPizzas (List input) {return input.stream (). filter (s) -> DeliverPizzaStatuses.contains (s.getStatus ())) .collect (Collectors.toList ()); } 
offentlig statisk EnumMap groupPizzaByStatus (Liste pzList) {EnumMap map = pzList.stream (). collect (Collectors.groupingBy (Pizza :: getStatus, () -> ny EnumMap (PizzaStatus.class), Collectors.toList ())); returkort; }

9. JSON repræsentation af Enum

Ved hjælp af Jackson-biblioteker er det muligt at have en JSON-repræsentation af enumtyper, som om de er POJO'er. Kodestykket nedenfor viser Jackson-kommentarerne, der kan bruges til det samme:

@JsonFormat (form = JsonFormat.Shape.OBJECT) offentlig enum PizzaStatus {BESTILT (5) {@Override offentlig boolsk erBestilles () {returner sand; }}, KLAR (2) {@Override public boolean isReady () {return true; }}, LEVERET (0) {@Override public boolean isDelivered () {return true; }}; privat int timeToDelivery; public boolean isOrdered () {return false;} public boolean isReady () {return false;} public boolean isDelivered () {return false;} @JsonProperty ("timeToDelivery") public int getTimeToDelivery () {return timeToDelivery; } privat PizzaStatus (int timeToDelivery) {this.timeToDelivery = timeToDelivery; }} 

Vi kan bruge Pizza og PizzaStatus som følger:

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

for at generere følgende JSON-repræsentation af Pizzas status:

{"status": {"timeToDelivery": 2, "ready": true, "order": false, "Delivered": false}, "Deliverable": True}

For mere information om JSON serialisering / deserialisering (inklusive tilpasning) af enumtyper henvises til Jackson - Serialize Enums som JSON Objects.

10. Konklusion

I denne artikel udforskede vi Java enum, fra sprogets grundlæggende til mere avancerede og interessante sager i den virkelige verden.

Kodestykker fra denne artikel kan findes i Github-arkivet.