Proširivanje enuma u Javi

1. Pregled

Tip enum, uveden u Javi 5, poseban je tip podataka koji predstavlja skupinu konstanti.

Koristeći enume, možemo definirati i koristiti naše konstante na način sigurnosti tipa. Donosi konstantama provjeru vremena kompajliranja.

Dalje, omogućuje nam upotrebu konstanti u preklopno kućište izjava.

U ovom uputstvu razgovarat ćemo o proširivanju nabrajanja u Javi, na primjer, dodavanju novih konstantnih vrijednosti i novih funkcionalnosti.

2. Enumeri i nasljeđivanje

Kada želimo proširiti Java klasu, obično kreiramo podrazred. U Javi su i nabrajanja klase.

U ovom odjeljku, hajde da vidimo možemo li naslijediti enum kao što to činimo s redovnim Java klasama.

2.1. Proširenje tipa Enum

Prije svega, pogledajmo primjer kako bismo brzo shvatili problem:

public enum BasicStringOperation {TRIM ("Uklanjanje početnih i pratećih razmaka."), TO_UPPER ("Promjena svih znakova u velika slova."), REVERSE ("Obrtanje zadanog niza."); opis privatnog niza; // konstruktor i getter}

Kao što gornji kod pokazuje, imamo nabrajanje BasicStringOperation koja sadrži tri osnovne operacije niza.

Recimo sada da želimo dodati neko proširenje nabrajanju, kao što je MD5_ENCODE i BASE64_ENCODE. Možda ćemo doći do ovog izravnog rješenja:

javni popis ExtendedStringOperation proširuje BasicStringOperation {MD5_ENCODE ("Kodiranje datog niza pomoću MD5 algoritma."), BASE64_ENCODE ("Kodiranje datog niza pomoću algoritma BASE64."); opis privatnog niza; // konstruktor i getter}

Međutim, kada pokušamo kompajlirati klasu, vidjet ćemo pogrešku kompajlera:

Ne može se naslijediti iz enum BasicStringOperation

2.2. Nasljeđivanje nije dopušteno za Enume

Hajde sada da saznamo zašto smo dobili pogrešku našeg prevoditelja.

Kada kompajliramo enum, Java kompajler mu čini neku čaroliju:

  • Enum pretvara u podrazred apstraktne klase java.lang.Enum
  • Kompilira enum kao a konačni razred

Na primjer, ako rastavimo naš sastavljeni BasicStringOperation enum pomoću javap, vidjet ćemo da je predstavljen kao podrazred od java.lang.Enum:

$ javap BasicStringOperation javna završna klasa com.baeldung.enums.extendenum.BasicStringOperation proširuje java.lang.Enum {javna statička završna com.baeldung.enums.extendenum.BasicStringOperation TRIM; javni statički konačni com.baeldung.enums.extendenum.BasicStringOperation TO_UPPER; javni statički konačni com.baeldung.enums.extendenum.BasicStringOperation REVERSE; ...} 

Kao što znamo, ne možemo naslijediti a konačni razred u Javi. Štoviše, čak i ako bismo mogli stvoriti ExtendedStringOperation enum naslijediti BasicStringOperation, naš ExtendedStringOperation enum bi proširio dvije klase: BasicStringOperation i java.lang.Enum. To će reći, to bi postalo situacija višestrukog nasljeđivanja, što Java ne podržava.

3. Emuliraj proširive nabrojeve sa sučeljima

Saznali smo da ne možemo stvoriti podrazred postojećeg popisa. Međutim, sučelje je proširivo. Stoga, možemo oponašati proširive enume implementiranjem sučelja.

3.1. Oponašati produžavanje konstanti

Da bismo brzo razumjeli ovu tehniku, pogledajmo kako oponašati širenje našeg BasicStringOperation enum imati MD5_ENCODE i BASE64_ENCODE operacijama.

Prvo, izradimo sučeljeStringOperation:

javno sučelje StringOperation {String getDescription (); } 

Dalje, napravimo da oba enuma implementiraju gornje sučelje:

javni enum BasicStringOperation provodi StringOperation {TRIM ("Uklanjanje početnih i pratećih razmaka."), TO_UPPER ("Promjena svih znakova u velika slova."), REVERSE ("Obrtanje zadanog niza."); opis privatnog niza; // konstruktor i getter override} javni nabroj ExtendedStringOperation implementira StringOperation {MD5_ENCODE ("Kodiranje datog niza pomoću algoritma MD5."), BASE64_ENCODE ("Kodiranje datog niza pomoću algoritma BASE64."); opis privatnog niza; // nadjačavanje konstruktora i gettera} 

Na kraju, pogledajmo kako oponašati proširivi BasicStringOperation nabrajanje.

Recimo da u našoj aplikaciji imamo metodu za dobivanje opisa BasicStringOperation nabrajanje:

javna klasa Application {public String getOperationDescription (BasicStringOperation stringOperation) {return stringOperation.getDescription (); }} 

Sada možemo promijeniti vrstu parametra BasicStringOperation u tip sučelja StringOperation kako bi metoda prihvatila instance iz oba nabrajanja:

javni String getOperationDescription (StringOperation stringOperation) {return stringOperation.getDescription (); }

3.2. Proširivanje funkcionalnosti

Vidjeli smo kako oponašati proširivanje konstanti enuma sa sučeljima.

Nadalje, sučelju također možemo dodati metode za proširenje funkcionalnosti nabrajanja.

Na primjer, želimo proširiti naš StringOperation nabraja tako da svaka konstanta može stvarno primijeniti operaciju na zadani niz:

javna klasa Application {javni niz applyOperation (operacija StringOperation, ulaz niza) {return operation.apply (input); } // ...} 

Da bismo to postigli, prvo dodajmo primijeniti () metoda sučelju:

javno sučelje StringOperation {String getDescription (); Primijeni niz (unos niza); } 

Dalje, puštamo svakoga StringOperation enum provodi ovu metodu:

javni enum BasicStringOperation implementira StringOperation {TRIM ("Uklanjanje vodećih i pratećih razmaka.") {@Override javni String primijeniti (String input) {return input.trim (); }}, TO_UPPER ("Promjena svih znakova u velika slova.") {@Premjena javnog niza primjenjuje se (unos niza) {return input.toUpperCase (); }}, REVERSE ("Poništavanje zadanog niza.") {@Override javni niz primjenjuje se (String input) {return new StringBuilder (input) .reverse (). ToString (); }}; // ...} javni enum ExtendedStringOperation implementira StringOperation {MD5_ENCODE ("Kodiranje datog niza pomoću MD5 algoritma.") {@ Override javni niz primjenjuje se (unos niza) {return DigestUtils.md5Hex (input); }}, BASE64_ENCODE ("Kodiranje datog niza pomoću algoritma BASE64.") {@Premjenjivanje javnog niza primjenjuje se (unos niza) {vrati novi niz (novi Base64 (). Kodiranje (input.getBytes ())); }}; // ...} 

Test metoda dokazuje da ovaj pristup djeluje onako kako smo očekivali:

@Test javna praznina givenAStringAndOperation_whenApplyOperation_thenGetExpectedResult () {String input = "hello"; Niz očekivanToUpper = "POZDRAV"; Niz očekivanReverse = "olleh"; Niz očekivanTrim = "zdravo"; Niz očekivanBase64 = "IGhlbGxv"; Niz očekujeMd5 = "292a5af68d31c10e31ad449bd8f51263"; assertEquals (očekuje seTrim, app.applyOperation (BasicStringOperation.TRIM, input)); assertEquals (očekuje seToUpper, app.applyOperation (BasicStringOperation.TO_UPPER, input)); assertEquals (očekuje se Reverse, app.applyOperation (BasicStringOperation.REVERSE, input)); assertEquals (očekuje seBase64, app.applyOperation (ExtendedStringOperation.BASE64_ENCODE, input)); assertEquals (očekuje seMd5, app.applyOperation (ExtendedStringOperation.MD5_ENCODE, input)); } 

4. Proširenje enum-a bez promjene koda

Naučili smo kako proširiti enum primjenom sučelja.

Međutim, ponekad želimo proširiti funkcionalnosti nabrajanja bez da ga mijenjamo. Na primjer, željeli bismo proširiti nabrajanje iz biblioteke treće strane.

4.1. Pridruživanje Enum konstanti i implementacije sučelja

Prvo, pogledajmo primjer nabrajanja:

javni popis ImmutableOperation {REMOVE_WHITESPACES, TO_LOWER, INVERT_CASE} 

Recimo da je nabrajanje iz vanjske knjižnice, stoga ne možemo promijeniti kôd.

Sada, u našem Primjena klase, želimo imati metodu za primjenu zadane operacije na ulazni niz:

javni String applyImmutableOperation (Operacija ImmutableOperation, unos niza) {...}

Budući da ne možemo promijeniti enum kod, možemo koristiti EnumMap za pridruživanje enumskih konstanti i potrebnih implementacija.

Prvo, kreirajmo sučelje:

operater javnog sučelja {String apply (String input); } 

Dalje ćemo stvoriti preslikavanje između enum konstanti i Operater implementacije pomoću EnumMap:

aplikacija javne klase {private static final Map OPERATION_MAP; statička {OPERATION_MAP = nova EnumMap (ImmutableOperation.class); OPERATION_MAP.put (ImmutableOperation.TO_LOWER, String :: toLowerCase); OPERATION_MAP.put (ImmutableOperation.INVERT_CASE, StringUtils :: swapCase); OPERATION_MAP.put (ImmutableOperation.REMOVE_WHITESPACES, input -> input.replaceAll ("\ s", "")); } javni String applyImmutableOperation (ImmutableOperation operacija, String input) {return operationMap.get (operacija) .apply (input); }

Na ovaj način, naš applyImmutableOperation () metoda može primijeniti odgovarajuću operaciju na zadani ulazni niz:

@Test javna praznina givenAStringAndImmutableOperation_whenApplyOperation_thenGetExpectedResult () {String input = "He ll O"; Niz očekivanToLower = "on će se o"; Niz očekujeRmWhitespace = "Pakao"; Niz očekujeInvertCase = "hE LL o"; assertEquals (očekuje seToLower, app.applyImmutableOperation (ImmutableOperation.TO_LOWER, input)); assertEquals (očekuje seRmWhitespace, app.applyImmutableOperation (ImmutableOperation.REMOVE_WHITESPACES, input)); assertEquals (očekuje se InvertCase, app.applyImmutableOperation (ImmutableOperation.INVERT_CASE, input)); } 

4.2. Provjera valjanosti EnumMap Objekt

Sada, ako je nabrajanje iz vanjske knjižnice, ne znamo je li promijenjeno ili ne, na primjer dodavanjem novih konstanti nabrajanju. U ovom slučaju, ako ne promijenimo inicijalizaciju datoteke EnumMap da sadrži novu vrijednost nabrajanja, našu EnumMap pristup može naići na problem ako se novo dodana enumska konstanta proslijedi našoj aplikaciji.

Da bismo to izbjegli, možemo potvrditi EnumMap nakon inicijalizacije da provjeri sadrži li sve enumske konstante:

statička {OPERATION_MAP = nova EnumMap (ImmutableOperation.class); OPERATION_MAP.put (ImmutableOperation.TO_LOWER, String :: toLowerCase); OPERATION_MAP.put (ImmutableOperation.INVERT_CASE, StringUtils :: swapCase); // ImmutableOperation.REMOVE_WHITESPACES nije mapiran ako (Arrays.stream (ImmutableOperation.values ​​()). AnyMatch (it ->! OPERATION_MAP.containsKey (it))) {baciti novi IllegalStateException ("Pronađena je neprekinuta konstanta enuma!"); }} 

Kao što pokazuje gornji kod, ako postoji konstanta od Nepromjenjiva operacija nije mapiran, an IllegalStateException bit će bačen. Budući da je naša provjera valjanosti u a statički blok, IllegalStateException bit će uzrok ExceptionInInitializerError:

@Test javna praznina givenUnmappedImmutableOperationValue_whenAppStarts_thenGetException () {Throwable throwable = assertThrows (ExceptionInInitializerError.class, () -> {ApplicationWithEx appEx = new ApplicationWithEx ();}); assertTrue (throwwable.getCause () instanceof IllegalStateException); } 

Dakle, kad se aplikacija ne uspije pokrenuti sa spomenutom pogreškom i uzrokom, trebali bismo ponovno provjeriti Nepromjenjiva operacija kako bi bili sigurni da su mapirane sve konstante.

5. Zaključak

Enum je posebna vrsta podataka u Javi. U ovom smo članku razgovarali zašto enum ne podržava nasljeđivanje. Nakon toga, pozabavili smo se načinom oponašanja proširivih nabrajanja sa sučeljima.

Također, naučili smo kako proširiti funkcionalnosti enuma bez da ga mijenjamo.

Kao i uvijek, puni izvorni kôd članka dostupan je na GitHub-u.