Útmutató a Java Enums-hez

1. Áttekintés

Ebben a cikkben megnézzük, hogy mik a Java-enumok, milyen problémákat oldanak meg, és a tervezési minták egy részét hogyan lehet a gyakorlatban használni.

A enum kulcsszó került bevezetésre a Java 5-ben. Egy speciális osztálytípust jelöl, amely mindig kiterjeszti a java.lang.Enum osztály. A használatukról szóló hivatalos dokumentációért tekintse meg a dokumentációt.

Az így definiált konstansok könnyebben olvashatóvá teszik a kódot, lehetővé teszik a fordítási idő ellenőrzését, előre dokumentálják az elfogadott értékek listáját és elkerülik az érvénytelen értékek miatt bekövetkező váratlan viselkedést.

Itt van egy gyors és egyszerű példa egy pékségre, amely meghatározza a pizza megrendelés állapotát; a megrendelés állapota lehet MEGRENDELT, KÉSZ vagy SZÁLLÍTOTT:

public enum PizzaStatus {RENDELT, KÉSZ, SZÁLLÍTOTT; }

Ezenkívül számos hasznos módszerrel járnak, amelyeket egyébként magának kellene megírnia, ha hagyományos nyilvános statikus végállandókat használna.

2. Egyéni Enum módszerek

Rendben, így most, miután alaposan megértettük, hogy mi az az enum, és hogyan használhatja őket, vigyük az előző példánkat a következő szintre, meghatározva néhány további API-módszert az enumon:

nyilvános osztály Pizza {privát PizzaStatus státusz; public enum PizzaStatus {RENDELT, KÉSZ, SZÁLLÍTOTT; } public boolean isDeliverable () {if (getStatus () == PizzaStatus.READY) {return true; } return false; } // Az állapotváltozót beállító és lekérő módszerek. } 

3. Enum típusok összehasonlítása az “==” operátor használatával

Mivel az enum típusok biztosítják, hogy az állandókból csak egy példány létezik a JVM-ben, biztonságosan használhatjuk az „==” operátort két változó összehasonlítására, amint az a fenti példában látható; ráadásul a „==” operátor fordítási és futási biztonságot nyújt.

Először nézzük meg a futásidejű biztonságnál a következő részletben, ahol a „==” operátort használják az állapotok és a NullPointerException nem dobódik el, ha bármelyik érték megegyezik nulla. Fordítva az an NullPointerException akkor dobják meg, ha az egyenlő módszert alkalmazzák:

if (testPz.getStatus (). egyenlő (Pizza.PizzaStatus.DELIVERED)); if (testPz.getStatus () == Pizza.PizzaStatus.DELIVERED); 

Ami időbiztonság összeállítása, nézzünk meg egy másik példát, ahol egy más típusú enumot hasonlítunk össze a egyenlő módszer igaznak bizonyul - mert az enum és a getStatus módszer véletlenül ugyanazok, de logikailag az összehasonlításnak hamisnak kell lennie. Ezt a problémát elkerülheti a „==” operátor használata.

A fordító inkompatibilitási hibaként jelöli meg az összehasonlítást:

if (testPz.getStatus (). egyenlő (TestColor.GREEN)); if (testPz.getStatus () == TestColor.GREEN); 

4. Enum típusok használata a Switch utasításokban

Enum típusok használhatók a kapcsoló nyilatkozatok is:

public int getDeliveryTimeInDays () {switch (status) {case ORDERED: return 5; eset KÉSZ: return 2; eset leszállítva: return 0; } return 0; }

5. Területek, módszerek és kivitelezők az Enums-ben

Meghatározhat konstruktorokat, módszereket és mezőket az enum típusok belsejében, amelyek nagyon erősvé teszik.

Bővítsük a fenti példát, és valósítsuk meg az átmenetet a pizza egyik szakaszából a másikba, és nézzük meg, hogyan tudunk megszabadulni a pizzától ha nyilatkozat és kapcsoló korábban használt nyilatkozat:

nyilvános osztály Pizza {privát PizzaStatus státusz; public enum PizzaStatus {ORDERED (5) {@Orride public boolean isOrdered () {return true; }}, READY (2) {@Orride public boolean isReady () {return true; }}, DELIVERED (0) {@Orride 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 logikai isDeliverable () {return this.status.isReady (); } public void printTimeToDeliver () {System.out.println ("A kézbesítés ideje" + this.getStatus (). getTimeToDelivery ()); } // Az állapotváltozót beállító és lekérő módszerek. } 

Az alábbi tesztrészlet bemutatja ennek működését:

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

6. EnumSet és EnumMap

6.1. EnumSet

A EnumSet szakosodott Készlet megvalósításának szánták Enum típusok.

Ez egy nagyon hatékony és kompakt reprezentáció egy adott személy számára Készlet nak,-nek Enum konstansok a HashSet, a belső miatt Bit vektor ábrázolás hogy használják. És típusbiztonságos alternatívát nyújt a hagyományosnál intalapú „bit zászlók”, lehetővé téve számunkra, hogy olvashatóbb és karbantarthatóbb tömör kódot írjunk.

A EnumSet egy absztrakt osztály, amelynek két megvalósítása van RegularEnumSet és JumboEnumSet, amelyek közül az egyiket a példányszámításkor az enum állandók számától függően választjuk.

Ezért mindig célszerű ezt a készletet használni, amikor a legtöbb forgatókönyvben enum konstansok gyűjteményével akarunk dolgozni (például albeállítás, hozzáadás, eltávolítás és tömeges műveletekhez, például tartalmazzaAll és összes eltávolítása) és felhasználása Enum.values ​​() ha csak iterálni akar az összes lehetséges konstanson.

Az alábbi kódrészletben láthatja, hogyan EnumSet állandók alkészletének és használatának létrehozására szolgál:

nyilvános osztály Pizza {privát statikus EnumSet undeliveredPizzaStatuses = EnumSet.of (PizzaStatus.ORDERED, PizzaStatus.READY); privát PizzaStatus státusz; public enum PizzaStatus {...} public boolean isDeliverable () {return this.status.isReady (); } public void printTimeToDeliver () {System.out.println ("A kézbesítés ideje" + this.getStatus (). getTimeToDelivery () + "nap"); } public static List getAllUndeliveredPizzas (List input) {return input.stream (). filter (s) -> undeliveredPizzaStatuses.contains (s.getStatus ())) .collect (Collectors.toList ()); } public void delivery () {if (isDeliverable ()) {PizzaDeliverySystemConfiguration.getInstance (). getDeliveryStrategy () .deliver (this); this.setStatus (PizzaStatus.DELIVERED); }} // Az állapotváltozót beállító és lekérő módszerek. } 

A következő teszt végrehajtása megmutatta a EnumSet a Készlet felület:

@Test public void givenPizaOrders_whenRetrievingUnDeliveredPzs_thenCorrectlyRetrieved () {List pzList = new ArrayList (); Pizza pz1 = új Pizza (); pz1.setStatus (Pizza.PizzaStatus.DELIVERED); Pizza pz2 = új Pizza (); pz2.setStatus (Pizza.PizzaStatus.RENDELT); Pizza pz3 = új Pizza (); pz3.setStatus (Pizza.PizzaStatus.RENDELT); Pizza pz4 = új Pizza (); pz4.setStatus (Pizza.PizzaStatus.READY); pzList.add (pz1); pzList.add (pz2); pzList.add (pz3); pzList.add (pz4); List undeliveredPzs = Pizza.getAllUndeliveredPizzas (pzList); assertTrue (kézbesítetlenPzs.size () == 3); }

6.2. EnumMap

EnumMap szakosodott Térkép megvalósítás, amelyet enum konstansokkal kell használni kulcsként. Hatékony és kompakt kivitel a társához képest HashMap és belsőleg tömbként van ábrázolva:

EnumMap térkép; 

Vessünk egy gyors pillantást egy valódi példára, amely megmutatja, hogyan használható a gyakorlatban:

nyilvános statikus EnumMap groupPizzaByStatus (List pizzaList) {EnumMap pzByStatus = új EnumMap(PizzaStatus.osztály); mert (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; } 

A következő teszt végrehajtása megmutatta a EnumMap a Térkép felület:

@Test public void givenPizaOrders_whenGroupByStatusCalled_thenCorrectlyGrouped () {List pzList = new ArrayList (); Pizza pz1 = új Pizza (); pz1.setStatus (Pizza.PizzaStatus.DELIVERED); Pizza pz2 = új Pizza (); pz2.setStatus (Pizza.PizzaStatus.RENDELT); Pizza pz3 = új Pizza (); pz3.setStatus (Pizza.PizzaStatus.RENDELT); Pizza pz4 = új Pizza (); pz4.setStatus (Pizza.PizzaStatus.READY); pzList.add (pz1); pzList.add (pz2); pzList.add (pz3); pzList.add (pz4); EnumMap térkép = 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. Végezze el a tervezési mintákat Enums használatával

7.1. Singleton minta

Normális esetben egy osztály megvalósítása a Singleton-minta segítségével nem triviális. Az Enums egyszerű és gyors módszert kínál a szingulettek megvalósítására.

Ezen felül, mivel az enum osztály végrehajtja a Sorosítható interfész a motorháztető alatt, az osztály garantáltan egyedülálló lesz a JVM részéről, amely ellentétben a hagyományos megvalósítással, ahol biztosítanunk kell, hogy a deserializáció során ne hozzanak létre új példányokat.

Az alábbi kódrészletben láthatjuk, hogyan tudjuk megvalósítani a szingulett mintát:

public enum PizzaDeliverySystemConfiguration {INSTANCE; PizzaDeliverySystemConfiguration () {// Inicializálási konfiguráció, amely magában foglalja // felülbíráló alapértelmezéseket, például a kézbesítési stratégia} privát PizzaDeliveryStrategy deliveryStrategy = PizzaDeliveryStrategy.NORMAL; public static PizzaDeliverySystemConfiguration getInstance () {return INSTANCE; } public PizzaDeliveryStrategy getDeliveryStrategy () {return deliveryStrategy; }}

7.2. Stratégiai minta

Hagyományosan a stratégiai mintát úgy írják, hogy egy interfésszel rendelkezik, amelyet különböző osztályok valósítanak meg.

Új stratégia hozzáadása új megvalósítási osztály hozzáadását jelentette. Az enums segítségével ezt kevesebb erőfeszítéssel érik el, egy új implementáció hozzáadása csak egy másik példány definiálását jelenti némi megvalósítással.

Az alábbi kódrészlet bemutatja a stratégiai minta megvalósítását:

public enum PizzaDeliveryStrategy {EXPRESS {@Orride public void delivery (Pizza pz) {System.out.println ("A pizzát expressz módban szállítjuk"); }}, NORMAL {@Orride public void delivery (Pizza pz) {System.out.println ("A pizza normál üzemmódban kerül kiszállításra"); }}; nyilvános absztrakt érvénytelenítés (Pizza pz); }

Adja hozzá a következő módszert a pizza osztály:

public void delivery () {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 és Enums

A pizza osztály átírható a Java 8-ba, és láthatja, hogy a módszerek hogyan getAllUndeliveredPizzas () és groupPizzaByStatus () olyan tömör lesz a lambdas és az Folyam API-k:

public static List getAllUndeliveredPizzas (List input) {return input.stream (). filter (s) ->! deliveryPizzaStatuses.contains (s.getStatus ())) .collect (Collectors.toList ()); } 
nyilvános statikus EnumMap groupPizzaByStatus (List pzList) {EnumMap map = pzList.stream (). collect (Collectors.groupingBy (Pizza :: getStatus, () -> new EnumMap (PizzaStatus.class, Collectors.toList ())); visszatérő térkép; }

9. Az Enum JSON képviselete

A Jackson könyvtárak használatával lehetséges, hogy JSON ábrázolják az enum típusokat, mintha POJO-k lennének. Az alábbi kódrészlet az ugyanazon felhasználható Jackson annotációkat mutatja:

@JsonFormat (shape = JsonFormat.Shape.OBJECT) public enum PizzaStatus {ORDERED (5) {@Orride public boolean isOrdered () {return true; }}, READY (2) {@Orride public boolean isReady () {return true; }}, DELIVERED (0) {@Orride 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; } privát PizzaStatus (int timeToDelivery) {this.timeToDelivery = timeToDelivery; }} 

A Pizza és a PizzaStatus a következőképpen használhatjuk:

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

a következő JSON reprezentáció előállításához pizzas állapota:

{"status": {"timeToDelivery": 2, "kész": igaz, "rendelt": hamis, "kézbesített": hamis}, "kézbesíthető": igaz}

Az enum típusok JSON sorozatszerűsítéséről / deszerializálásáról (beleértve a testreszabást is) a Jackson - Serialize Enums néven JSON Objects néven olvashat.

10. Következtetés

Ebben a cikkben a Java enumot vizsgáltuk, a nyelv alapjaitól kezdve a fejlettebb és érdekesebb valós használati esetekig.

A cikk kódrészletei a Github adattárban találhatók.