Vodič za Java Enume

1. Pregled

U ovom ćemo članku vidjeti što su Java enume, koji problemi rješavaju i kako se neki od dizajnerskih obrazaca mogu koristiti u praksi.

The nabrajanje ključna riječ uvedena je u Javi 5. Označava posebnu vrstu klase koja uvijek proširuje java.lang.Enum razred. Za službenu dokumentaciju o njihovoj uporabi pogledajte dokumentaciju.

Konstante definirane na ovaj način čine kôd čitljivijim, omogućuju provjeru vremena kompajliranja, dokumentiraju unaprijed popis prihvaćenih vrijednosti i izbjegavaju neočekivano ponašanje zbog prenošenja nevaljanih vrijednosti.

Evo kratkog i jednostavnog primjera nabrajanja koji definira status narudžbe za pizzu; status narudžbe može biti NARUČENO, SPREMAN ili DOSTAVLJENO:

javni popis PizzaStatus {NARUČENO, SPREMNO, DOSTAVLJENO; }

Uz to, dolaze s mnogim korisnim metodama, koje biste inače morali sami napisati da koristite tradicionalne javne statičke konačne konstante.

2. Prilagođene metode nabrajanja

U redu, sada, kad smo osnovno razumjeli što su nabroji i kako ih možete koristiti, uzmimo naš prethodni primjer na sljedeću razinu definiranjem nekih dodatnih API metoda na nabrajanju:

javna klasa Pizza {status privatnog PizzaStatusa; javni popis PizzaStatus {NARUČENO, SPREMNO, DOSTAVLJENO; } public boolean isDeliverable () {if (getStatus () == PizzaStatus.READY) {return true; } return false; } // Metode koje postavljaju i dobivaju statusnu varijablu. } 

3. Usporedba vrsta nabrajanja pomoću operatora “==”

Budući da tipovi nabrajanja osiguravaju postojanje samo jedne instance konstanti u JVM-u, možemo sigurno upotrijebiti operator = == za usporedbu dviju varijabli kao što je vidljivo u gornjem primjeru; štoviše, operater “==” osigurava sigurnost pri kompajliranju i vremenu izvođenja.

Pogledajmo prvo pri sigurnosti tijekom rada u sljedećem isječku gdje se operator "==" koristi za usporedbu statusa i a NullPointerException neće biti bačena ako je bilo koja vrijednost null. Suprotno tome, an NullPointerException bacilo bi se ako se koristi metoda jednakih:

ako (testPz.getStatus (). jednako (Pizza.PizzaStatus.DELIVERED)); if (testPz.getStatus () == Pizza.PizzaStatus.DELIVERED); 

Što se tiče sastaviti sigurnost vremena, pogledajmo još jedan primjer gdje se uspoređuje nabrajanje drugog tipa pomoću jednako metoda utvrđena je kao istinita - jer su vrijednosti enuma i getStatus slučajno su iste metode, ali logično bi usporedba trebala biti lažna. Ovaj se problem izbjegava upotrebom operatora "==".

Prevoditelj će usporedbu označiti kao pogrešku nekompatibilnosti:

ako (testPz.getStatus (). je jednako (TestColor.GREEN)); if (testPz.getStatus () == TestColor.GREEN); 

4. Korištenje tipova Enum u izjavama prebacivanja

Tipovi enuma mogu se koristiti u a sklopka izjave također:

public int getDeliveryTimeInDays () {prekidač (status) {case NARUČENO: return 5; slučaj SPREMAN: povratak 2; slučaj DOSTAVLJEN: povratak 0; } return 0; }

5. Polja, metode i konstruktori u Enumsu

Možete definirati konstruktore, metode i polja unutar tipova nabrajanja koji ga čine vrlo moćnim.

Proširimo gornji primjer i provedimo prijelaz s jedne faze pizze na drugu i vidjet ćemo kako se možemo riješiti ako izjava i sklopka izjava korištena prije:

javna klasa Pizza {status privatnog PizzaStatusa; public enum PizzaStatus {NARUČENO (5) {@Premjesti javno boolean isOrdered () {return true; }}, SPREMNO (2) {@Prevjeri javni boolean isReady () {return true; }}, DOSTAVLJENO (0) {@Override public boolean isDelivered () {return true; }}; privatni 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 ("Vrijeme isporuke je" + this.getStatus (). getTimeToDelivery ()); } // Metode koje postavljaju i dobivaju statusnu varijablu. } 

Testni isječak u nastavku pokazuje kako to funkcionira:

@Test javna praznina givenPizaOrder_whenReady_thenDeliverable () {Pizza testPz = nova Pizza (); testPz.setStatus (Pizza.PizzaStatus.READY); assertTrue (testPz.isDeliverable ()); }

6. EnumSet i EnumMap

6.1. EnumSet

The EnumSet je specijalizirana Postavi implementacija namijenjena upotrebi Enum vrste.

To je vrlo učinkovit i kompaktan prikaz pojedinca Postavi od Enum konstante u usporedbi s a HashSet, zahvaljujući unutarnjem Zastupljenost vektora bitova koja se koristi. I pruža tipičnu zamjenu za tradicionalnu intbazirane na „bit zastavicama“, što nam omogućuje pisanje sažetog koda koji je čitljiviji i održiviji.

The EnumSet je apstraktna klasa koja se naziva dvije implementacije RegularEnumSet i JumboEnumSet, od kojih se jedna bira ovisno o broju konstanti u enumu u trenutku instanciranja.

Stoga je uvijek dobro koristiti ovaj skup kad god želimo raditi s kolekcijom enum konstanti u većini scenarija (poput podskupa, dodavanja, uklanjanja i za skupne operacije poput sadržiSve i ukloniti sve) i koristiti Enum.values ​​() ako samo želite ponoviti sve moguće konstante.

U isječku koda ispod možete vidjeti kako EnumSet koristi se za stvaranje podskupa konstanti i njegove upotrebe:

javna klasa Pizza {private static EnumSet undeliveredPizzaStatuses = EnumSet.of (PizzaStatus.ORDERED, PizzaStatus.READY); privatni status PizzaStatusa; public enum PizzaStatus {...} public boolean isDeliverable () {return this.status.isReady (); } public void printTimeToDeliver () {System.out.println ("Vrijeme isporuke je" + this.getStatus (). getTimeToDelivery () + "days"); } javni statički Popis getAllUndeliveredPizzas (Unos popisa) {return input.stream (). filter (s) -> undeliveredPizzaStatuses.contens (s.getStatus ())) .collect (Collectors.toList ()); } javna void isporuka () {if (isDeliverable ()) {PizzaDeliverySystemConfiguration.getInstance (). getDeliveryStrategy () .deliver (this); this.setStatus (PizzaStatus.DELIVERED); }} // Metode koje postavljaju i dobivaju statusnu varijablu. } 

Izvršenje sljedećeg testa pokazalo je snagu EnumSet provedba Postavi sučelje:

@Test javna praznina givenPizaOrders_whenRetrievingUnDeliveredPzs_thenCorrectRetrieved () {List pzList = new ArrayList (); Pizza pz1 = nova Pizza (); pz1.setStatus (Pizza.PizzaStatus.DELIVERED); Pizza pz2 = nova Pizza (); pz2.setStatus (Pizza.PizzaStatus.ORDERED); Pizza pz3 = nova Pizza (); pz3.setStatus (Pizza.PizzaStatus.ORDERED); Pizza pz4 = nova Pizza (); pz4.setStatus (Pizza.PizzaStatus.READY); pzList.add (pz1); pzList.add (pz2); pzList.add (pz3); pzList.add (pz4); Popis undeliveredPzs = Pizza.getAllUndeliveredPizzas (pzList); assertTrue (undeliveredPzs.size () == 3); }

6.2. EnumMap

EnumMap je specijalizirana Karta implementacija namijenjena upotrebi s konstantama nabrajanja kao ključevima. Učinkovita je i kompaktna implementacija u usporedbi sa svojim kolegama HashMap i interno je predstavljen kao niz:

Karta EnumMap; 

Kratko ćemo pogledati stvarni primjer koji pokazuje kako se to može koristiti u praksi:

javna statička EnumMap groupPizzaByStatus (Popis pizzaList) {EnumMap pzByStatus = nova EnumMap(PizzaStatus.class); za (Pizza pz: pizzaList) {PizzaStatus status = pz.getStatus (); if (pzByStatus.containsKey (status)) {pzByStatus.get (status) .add (pz); } else {Popis newPzList = novi ArrayList (); newPzList.add (pz); pzByStatus.put (status, newPzList); }} return pzByStatus; } 

Izvršenje sljedećeg testa pokazalo je snagu EnumMap provedba Karta sučelje:

@Test javna praznina givenPizaOrders_whenGroupByStatusCalled_thenCorrectGrouped () {List pzList = new ArrayList (); Pizza pz1 = nova Pizza (); pz1.setStatus (Pizza.PizzaStatus.DELIVERED); Pizza pz2 = nova Pizza (); pz2.setStatus (Pizza.PizzaStatus.ORDERED); Pizza pz3 = nova Pizza (); pz3.setStatus (Pizza.PizzaStatus.ORDERED); Pizza pz4 = nova 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. Primijenite uzorke dizajna pomoću Enum-a

7.1. Jednokraki uzorak

Uobičajeno je da provedba klase pomoću Singleton uzorka prilično nije trivijalna. Enumi pružaju jednostavan i brz način implementacije pojedinačnih datoteka.

Uz to, budući da klasa enum provodi Serijalizirati sučelja ispod haube, JVM zajamčuje da je klasa singleton, što je za razliku od konvencionalne implementacije gdje moramo osigurati da se tijekom deserializacije ne stvore nove instance.

U isječku koda u nastavku vidimo kako možemo implementirati jednokračni uzorak:

javni popis PizzaDeliverySystemConfiguration {INSTANCE; PizzaDeliverySystemConfiguration () {// Konfiguracija inicijalizacije koja uključuje // nadjačavanje zadanih vrijednosti poput strategije isporuke} private PizzaDeliveryStrategy deliveryStrategy = PizzaDeliveryStrategy.NORMAL; javna statička PizzaDeliverySystemConfiguration getInstance () {return INSTANCE; } public PizzaDeliveryStrategy getDeliveryStrategy () {return deliveryStrategy; }}

7.2. Obrazac strategije

Uobičajeno je obrazac Strategije napisan tako da ima sučelje koje implementiraju različite klase.

Dodavanje nove strategije značilo je dodavanje nove klase implementacije. S enumima se to postiže s manje napora, dodavanje nove implementacije znači definiranje samo još jedne instance s nekom implementacijom.

Isječak koda u nastavku pokazuje kako primijeniti obrazac Strategije:

public enum PizzaDeliveryStrategy {EXPRESS {@Preuzmi javnu prazninu isporuke (Pizza pz) {System.out.println ("Pizza će biti isporučena u ekspresnom načinu"); }}, NORMALNO {@Preuzmi javnu isporuku praznina (Pizza pz) {System.out.println ("Pizza će se isporučivati ​​u normalnom načinu"); }}; javna sažetak dostava praznine (Pizza pz); }

Dodajte sljedeću metodu u Pizza razred:

javna praznina isporučiti () {if (isDeliverable ()) {PizzaDeliverySystemConfiguration.getInstance (). getDeliveryStrategy () .deliver (this); this.setStatus (PizzaStatus.DELIVERED); }}
@Test javna praznina givenPizaOrder_whenDelivered_thenPizzaGetsDeliveredAndStatusChanges () {Pizza pz = nova Pizza (); pz.setStatus (Pizza.PizzaStatus.READY); pz.deliver (); assertTrue (pz.getStatus () == Pizza.PizzaStatus.DELIVERED); }

8. Java 8 i Enums

The Pizza klase može se prepisati u Javi 8 i možete vidjeti kako metode getAllUndeliveredPizzas () i groupPizzaByStatus () postanite toliko jezgroviti s upotrebom lambda i Stream Apis:

javna statička lista getAllUndeliveredPizzas (ulaz s popisa) {return input.stream (). filter (s) ->! DeliveryPizzaStatuses.contens (s.getStatus ())) .collect (Collectors.toList ()); } 
javna statička EnumMap groupPizzaByStatus (Popis pzList) {EnumMap map = pzList.stream (). collect (Collectors.groupingBy (Pizza :: getStatus, () -> novi EnumMap (PizzaStatus.class), Collectors.toList ())); karta povratka; }

9. JSON zastupanje Enuma

Korištenjem Jacksonovih knjižnica moguće je imati JSON prikaz tipova nabrajanja kao da su POJO-ovi. Isječak koda u nastavku prikazuje Jacksonove bilješke koje se mogu koristiti za iste:

@JsonFormat (shape = JsonFormat.Shape.OBJECT) public enum PizzaStatus {NARUČENO (5) {@Override public boolean isOrdered () {return true; }}, SPREMNO (2) {@Prevjeri javni boolean isReady () {return true; }}, DOSTAVLJENO (0) {@Override public boolean isDelivered () {return true; }}; privatni int timeToDelivery; public boolean isOrdered () {return false;} public boolean isReady () {return false;} public boolean isDelivered () {return false;} @JsonProperty ("timeToDelivery") public int getTimeToDelivery () {return timeToDelivery; } privatni PizzaStatus (int timeToDelivery) {this.timeToDelivery = timeToDelivery; }} 

Pizzu i PizzaStatus možemo koristiti na sljedeći način:

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

generirati sljedeći JSON prikaz Pizzas status:

{"status": {"timeToDelivery": 2, "ready": true, "order": false, "isporučeno": false}, "deliverable": true}

Za više informacija o JSON-ovoj serializaciji / deserializaciji (uključujući prilagodbu) tipova nabrajanja pogledajte Jackson - Serialize Enums kao JSON objekte.

10. Zaključak

U ovom smo članku istražili Java enum, od osnova jezika do naprednijih i zanimljivijih slučajeva stvarne upotrebe.

Isječci koda iz ovog članka mogu se naći u spremištu Github.