Enums uitbreiden in Java

1. Overzicht

Het enum-type, geïntroduceerd in Java 5, is een speciaal gegevenstype dat een groep constanten vertegenwoordigt.

Met behulp van enums kunnen we onze constanten definiëren en gebruiken voor het type veiligheid. Het brengt controle tijdens het compileren naar de constanten.

Verder stelt het ons in staat om de constanten in de schakelkast uitspraak.

In deze tutorial bespreken we het uitbreiden van enums in Java, bijvoorbeeld het toevoegen van nieuwe constante waarden en nieuwe functionaliteiten.

2. Enums en erfenis

Als we een Java-klasse willen uitbreiden, maken we meestal een subklasse. In Java zijn enums ook klassen.

Laten we in deze sectie kijken of we een enum kunnen erven zoals we doen met gewone Java-klassen.

2.1. Een opsommingstype uitbreiden

Laten we eerst een voorbeeld bekijken, zodat we het probleem snel kunnen begrijpen:

openbare enum BasicStringOperation {TRIM ("Voorloop- en volgspaties verwijderen."), TO_UPPER ("Alle tekens in hoofdletters veranderen."), REVERSE ("De gegeven tekenreeks omkeren."); private String beschrijving; // constructor en getter}

Zoals de bovenstaande code laat zien, hebben we een opsomming BasicStringOperation dat drie basis string-operaties bevat.

Laten we nu zeggen dat we een extensie aan de enum willen toevoegen, zoals MD5_ENCODE en BASE64_ENCODE. We kunnen deze eenvoudige oplossing bedenken:

openbare enum ExtendedStringOperation breidt BasicStringOperation uit {MD5_ENCODE ("Codering van de gegeven string met behulp van het MD5-algoritme."), BASE64_ENCODE ("Codering van de gegeven string met behulp van het BASE64-algoritme."); private String beschrijving; // constructor en getter}

Wanneer we echter proberen de klasse te compileren, zien we de compilatiefout:

Kan niet erven van enum BasicStringOperation

2.2. Overerving is niet toegestaan ​​voor enums

Laten we nu eens kijken waarom we onze compilerfout hebben gekregen.

Wanneer we een enum compileren, doet de Java-compiler er wat magie aan:

  • Het verandert de opsomming in een subklasse van de abstracte klasse java.lang.Enum
  • Het compileert de enum als een laatste klasse

Als we bijvoorbeeld onze compiled BasicStringOperation enum gebruiken javap, we zullen zien dat het wordt weergegeven als een subklasse van java.lang.Enum:

$ javap BasicStringOperation openbare laatste klasse com.baeldung.enums.extendenum.BasicStringOperation breidt uit java.lang.Enum {openbare statische finale com.baeldung.enums.extendenum.BasicStringOperation TRIM; openbare statische finale com.baeldung.enums.extendenum.BasicStringOperation TO_UPPER; openbare statische finale com.baeldung.enums.extendenum.BasicStringOperation REVERSE; ...} 

Zoals we weten, kunnen we geen laatste klasse in Java. Bovendien, zelfs als we het ExtendedStringOperation opsomming om te erven BasicStringOperation, onze ExtendedStringOperation enum zou twee klassen uitbreiden: BasicStringOperation en java.lang.Enum. Dat wil zeggen, het zou een situatie van meervoudige overerving worden, die niet wordt ondersteund in Java.

3. Emuleer uitbreidbare enums met interfaces

We hebben geleerd dat we geen subklasse van een bestaande opsomming kunnen maken. Een interface is echter uitbreidbaar. Daarom we kunnen uitbreidbare enums emuleren door een interface te implementeren.

3.1. Emuleer het uitbreiden van de constanten

Laten we, om deze techniek snel te begrijpen, eens kijken hoe we onze BasicStringOperation opsomming te hebben MD5_ENCODE en BASE64_ENCODE operaties.

Laten we eerst een koppelStringOperation:

openbare interface StringOperation {String getDescription (); } 

Vervolgens laten we beide enums de bovenstaande interface implementeren:

openbare opsomming BasicStringOperation implementeert StringOperation {TRIM ("Voorloop- en volgspaties verwijderen."), TO_UPPER ("Alle tekens in hoofdletters veranderen."), REVERSE ("De gegeven tekenreeks omkeren."); private String beschrijving; // constructor and getter override} public enum ExtendedStringOperation implementeert StringOperation {MD5_ENCODE ("Codering van de gegeven string met behulp van het MD5-algoritme."), BASE64_ENCODE ("Codering van de opgegeven string met behulp van het BASE64-algoritme."); private String beschrijving; // constructor en getter overschrijven} 

Laten we tot slot eens kijken hoe we een uitbreidbaar bestand kunnen emuleren BasicStringOperation opsomming.

Laten we zeggen dat we een methode in onze applicatie hebben om de beschrijving van BasicStringOperation opsomming:

openbare klasse Toepassing {openbare String getOperationDescription (BasicStringOperation stringOperation) {retour stringOperation.getDescription (); }} 

Nu kunnen we het parametertype wijzigen BasicStringOperation in het interfacetype StringOperation om de methode instanties van beide enums te laten accepteren:

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

3.2. Functionaliteiten uitbreiden

We hebben gezien hoe uitbreidende constanten van enums kunnen worden geëmuleerd met interfaces.

Verder kunnen we ook methoden aan de interface toevoegen om de functionaliteiten van de enums uit te breiden.

Zo willen we onze StringOperation enums zodat elke constante de bewerking daadwerkelijk kan toepassen op een bepaalde string:

public class Application {public String applyOperation (StringOperation-bewerking, String-invoer) {return operation.apply (invoer); } // ...} 

Om dat te bereiken, voegen we eerst het van toepassing zijn() methode naar de interface:

openbare interface StringOperation {String getDescription (); String toepassen (String-invoer); } 

Vervolgens laten we elk StringOperation enum implementeer deze methode:

openbare enum BasicStringOperation implementeert StringOperation {TRIM ("Voorloop- en volgspaties verwijderen.") {@Override public String apply (String-invoer) {return input.trim (); }}, TO_UPPER ("Alle tekens in hoofdletters veranderen.") {@Override public String apply (String-invoer) {return input.toUpperCase (); }}, REVERSE ("De gegeven string omkeren.") {@Override public String apply (String input) {return new StringBuilder (input) .reverse (). ToString (); }}; // ...} public enum ExtendedStringOperation implementeert StringOperation {MD5_ENCODE ("Codering van de opgegeven string met behulp van het MD5-algoritme.") {@Override public String apply (String-invoer) {return DigestUtils.md5Hex (invoer); }}, BASE64_ENCODE ("Codering van de opgegeven string met behulp van het BASE64-algoritme.") {@Override public String apply (String-invoer) {return new String (nieuwe Base64 (). Encode (input.getBytes ())); }}; // ...} 

Een testmethode bewijst dat deze aanpak werkt zoals we verwacht hadden:

@Test openbare ongeldig gegevenAStringAndOperation_whenApplyOperation_thenGetExpectedResult () {String input = "hallo"; String verwachteToUpper = "HELLO"; String verwachteReverse = "olleh"; String verwachteTrim = "hallo"; String verwachteBase64 = "IGhlbGxv"; String verwachteMd5 = "292a5af68d31c10e31ad449bd8f51263"; assertEquals (verwachteTrim, app.applyOperation (BasicStringOperation.TRIM, invoer)); assertEquals (verwachteToUpper, app.applyOperation (BasicStringOperation.TO_UPPER, invoer)); assertEquals (verwachtReverse, app.applyOperation (BasicStringOperation.REVERSE, invoer)); assertEquals (verwachteBase64, app.applyOperation (ExtendedStringOperation.BASE64_ENCODE, invoer)); assertEquals (verwachteMd5, app.applyOperation (ExtendedStringOperation.MD5_ENCODE, invoer)); } 

4. Een opsomming uitbreiden zonder de code te wijzigen

We hebben geleerd hoe we een opsomming kunnen uitbreiden door interfaces te implementeren.

Soms willen we echter de functionaliteiten van een enum uitbreiden zonder deze te wijzigen. We willen bijvoorbeeld een enum uitbreiden van een bibliotheek van derden.

4.1. Enum-constanten en interface-implementaties associëren

Laten we eerst eens kijken naar een opsommingsvoorbeeld:

openbare opsomming ImmutableOperation {REMOVE_WHITESPACES, TO_LOWER, INVERT_CASE} 

Laten we zeggen dat de enum afkomstig is van een externe bibliotheek, daarom kunnen we de code niet wijzigen.

Nu, in onze Toepassing class, we willen een methode hebben om de gegeven bewerking toe te passen op de invoertekenreeks:

public String applyImmutableOperation (ImmutableOperation-bewerking, String-invoer) {...}

Omdat we de enum-code niet kunnen wijzigen, we kunnen gebruiken EnumMap om de opsommingsconstanten en vereiste implementaties te associëren.

Laten we eerst een interface maken:

openbare interface Operator {String toepassen (String-invoer); } 

Vervolgens maken we de mapping tussen enum-constanten en de Operator implementaties met behulp van een EnumMap:

openbare klasse Applicatie {privé statische definitieve kaart OPERATION_MAP; statische {OPERATION_MAP = nieuwe 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", "")); } public String applyImmutableOperation (ImmutableOperation-bewerking, String-invoer) {return operationMap.get (bewerking) .apply (invoer); }

Op deze manier is onze applyImmutableOperation () methode kan de overeenkomstige bewerking toepassen op de opgegeven invoertekenreeks:

@Test openbare ongeldig gegevenAStringAndImmutableOperation_whenApplyOperation_thenGetExpectedResult () {String input = "He ll O"; String verwachteToLower = "he ll o"; String verwachteRmWhitespace = "HellO"; String verwachteInvertCase = "hE LL o"; assertEquals (pectedToLower, app.applyImmutableOperation (ImmutableOperation.TO_LOWER, input)); assertEquals (verwachteRmWhitespace, app.applyImmutableOperation (ImmutableOperation.REMOVE_WHITESPACES, invoer)); assertEquals (verwachtInvertCase, app.applyImmutableOperation (ImmutableOperation.INVERT_CASE, invoer)); } 

4.2. Validatie van het EnumMap Voorwerp

Als de enum nu uit een externe bibliotheek komt, weten we niet of deze is gewijzigd of niet, bijvoorbeeld door nieuwe constanten aan de enum toe te voegen. Als we in dit geval onze initialisatie van het EnumMap om de nieuwe enum-waarde te bevatten, onze EnumMap benadering kan een probleem tegenkomen als de nieuw toegevoegde enum-constante wordt doorgegeven aan onze applicatie.

Om dat te voorkomen, kunnen we het EnumMap na de initialisatie om te controleren of het alle enum-constanten bevat:

statische {OPERATION_MAP = nieuwe EnumMap (ImmutableOperation.class); OPERATION_MAP.put (ImmutableOperation.TO_LOWER, String :: toLowerCase); OPERATION_MAP.put (ImmutableOperation.INVERT_CASE, StringUtils :: swapCase); // ImmutableOperation.REMOVE_WHITESPACES wordt niet toegewezen als (Arrays.stream (ImmutableOperation.values ​​()). AnyMatch (it ->! OPERATION_MAP.containsKey (it))) {throw new IllegalStateException ("Unmapped enum constant found!"); }} 

Zoals de bovenstaande code laat zien, is een eventuele constante from Onveranderlijke werking is niet in kaart gebracht, een IllegalStateException zal worden gegooid. Omdat onze validatie zich in een statisch blok, IllegalStateException zal de oorzaak zijn van ExceptionInInitializerError:

@Test openbare ongeldige gegevenUnmappedImmutableOperationValue_whenAppStarts_thenGetException () {Throwable throwable = assertThrows (ExceptionInInitializerError.class, () -> {ApplicationWithEx appEx = nieuwe ApplicationWithEx ();}); assertTrue (throwable.getCause () instantie van IllegalStateException); } 

Dus als de toepassing niet start met de genoemde fout en oorzaak, moeten we het Onveranderlijke werking om ervoor te zorgen dat alle constanten in kaart zijn gebracht.

5. Conclusie

De enum is een speciaal gegevenstype in Java. In dit artikel hebben we besproken waarom enum overerving niet ondersteunt. Daarna hebben we besproken hoe uitbreidbare enums met interfaces kunnen worden geëmuleerd.

We hebben ook geleerd hoe we de functionaliteiten van een enum kunnen uitbreiden zonder deze te wijzigen.

Zoals altijd is de volledige broncode van het artikel beschikbaar op GitHub.