Programmering

Sådan bruges typesafe enums i Java

Java-kode, der bruger traditionelle opregnede typer, er problematisk. Java 5 gav os et bedre alternativ i form af typesafe enums. I denne artikel introducerer jeg dig til opregnede typer og typesafe enums, viser dig, hvordan du erklærer et typesafe enum og bruger det i en switch-erklæring, og diskuterer tilpasning af en typesafe enum ved at tilføje data og adfærd. Jeg afslutter artiklen ved at udforske java.lang.Enum klasse.

download Hent koden Download kildekoden for eksempler i denne Java 101-tutorial. Oprettet af Jeff Friesen til JavaWorld /.

Fra opregnede typer til typesafe enums

En opregnet type angiver et sæt relaterede konstanter som dets værdier. Eksempler inkluderer en uges dage, standardretningen nord / syd / øst / vest kompas, en valutas møntværdier og en leksikalsk analysatorens token-typer.

Opregnede typer er traditionelt blevet implementeret som sekvenser af heltalskonstanter, hvilket demonstreres af følgende sæt retningskonstanter:

statisk endelig int DIR_NORTH = 0; statisk endelig int DIR_WEST = 1; statisk endelig int DIR_EAST = 2; statisk endelig int DIR_SOUTH = 3;

Der er flere problemer med denne tilgang:

  • Manglende typesikkerhed: Da en opregnet typekonstant bare er et heltal, kan ethvert heltal specificeres, hvor konstanten er påkrævet. Desuden kan addition, subtraktion og andre matematiske operationer udføres på disse konstanter; for eksempel, (DIR_NORTH + DIR_EAST) / DIR_SOUTH), hvilket er meningsløst.
  • Navneområde ikke til stede: En opregnet type konstanter skal have forud for en eller anden form for (forhåbentlig) unik identifikator (f.eks. DIR_) for at forhindre kollisioner med en anden opregnet type konstanter.
  • Skørhed: Da opregnede typekonstanter er samlet til klassefiler, hvor deres bogstavelige værdier er gemt (i konstante puljer), skal ændring af en konstants værdi kræve, at disse klassefiler og de applikationsklassefiler, der er afhængige af dem, genopbygges. Ellers vil udefineret adfærd forekomme ved kørsel.
  • Mangel på information: Når en konstant udskrives, afgives dens heltal værdi. Denne output fortæller dig intet om, hvad heltalets værdi repræsenterer. Det identificerer ikke engang den opregnede type, som konstanten hører til.

Du kunne undgå “manglen på typesikkerhed” og “manglende information” ved at bruge java.lang.Streng konstanter. For eksempel kan du angive statisk endelig String DIR_NORTH = "NORTH";. Selvom den konstante værdi er mere meningsfuld, Snor-baserede konstanter lider stadig under “navneområde ikke til stede” og skørhedsproblemer. I modsætning til heltalssammenligninger kan du heller ikke sammenligne strengværdier med == og != operatører (som kun sammenligner referencer).

Disse problemer fik udviklere til at opfinde et klassebaseret alternativ kendt som Typesafe Enum. Dette mønster er blevet beskrevet og kritiseret bredt. Joshua Bloch introducerede mønsteret i artikel 21 i hans Effektiv Java-programmeringssprogguide (Addison-Wesley, 2001) og bemærkede, at den har nogle problemer; nemlig at det er akavet at samle typesafe enum-konstanter i sæt, og at optællingskonstanter ikke kan bruges i kontakt udsagn.

Overvej følgende eksempel på typesafe enum mønster. Det Dragt klasse viser, hvordan du kan bruge det klassebaserede alternativ til at introducere en opregnet type, der beskriver de fire kortdragter (køller, diamanter, hjerter og spar):

offentlig finaleklasse Suit // Bør ikke være i stand til at underklasse Suit. {offentlig statisk endelig dragt CLUBS = ny dragt (); offentlig statisk endelig dragt DIAMONDS = ny dragt (); offentlig statisk endelig dragt HJERTER = ny dragt (); offentlig statisk endelig dragt SPADES = ny dragt (); private Suit () {} // Bør ikke være i stand til at introducere yderligere konstanter. }

For at bruge denne klasse ville du introducere en Dragt variabel og tildel den til en af DragtKonstanter, som følger:

Suit suit = Suit.DIAMONDS;

Du vil måske måske forhøre dig dragt i en kontakt udsagn som denne:

switch (jakkesæt) {case Suit.CLUBS: System.out.println ("klubber"); pause; sag Suit.DIAMONDS: System.out.println ("diamanter"); pause; sag Suit.HEARTS: System.out.println ("hjerter"); pause; sag Suit.SPADES: System.out.println ("spader"); }

Men når Java-kompileren møder Dragter. CLUBS, rapporterer den en fejl, der siger, at der kræves et konstant udtryk. Du kan prøve at løse problemet som følger:

switch (jakkesæt) {case CLUBS: System.out.println ("klubber"); pause; sag DIAMANTER: System.out.println ("diamanter"); pause; sag HEARTS: System.out.println ("hjerter"); pause; sag SPADES: System.out.println ("spader"); }

Dog når compileren møder KLUBBER, vil den rapportere en fejl, der siger, at den ikke kunne finde symbolet. Og selvom du placerede Dragt i en pakke, importerede pakken og statisk importerede disse konstanter, ville compileren klage over, at den ikke kan konvertere Dragt til int når man støder på dragt i switch (kulør). Med hensyn til hver sag, ville compileren også rapportere, at der kræves et konstant udtryk.

Java understøtter ikke Typesafe Enum-mønsteret med kontakt udsagn. Imidlertid introducerede den typesafe enum sprogfunktion for at indkapsle fordelene ved mønsteret, mens problemet løses, og denne funktion understøtter kontakt.

Erklæring om en typesafe enum og brug af den i en switch-erklæring

En simpel typesafe enum-erklæring i Java-kode ligner dens modstykker på sprogene C, C ++ og C #:

enum Direction {NORTH, WEST, EAST, SOUTH}

Denne erklæring bruger nøgleordet enum at introducere Retning som en typesafe enum (en særlig type klasse), hvor vilkårlige metoder kan tilføjes og vilkårlige grænseflader kan implementeres. Det NORD, VEST, ØSTog SYDenum konstanter implementeres som konstant-specifikke klasseorganer, der definerer anonyme klasser, der udvider den indesluttende Retning klasse.

Retning og andre typesafe enums udvides Enum og arve forskellige metoder, herunder værdier (), toString ()og sammenligne med(), fra denne klasse. Vi udforsker Enum senere i denne artikel.

Listing 1 erklærer ovennævnte enum og bruger det i en kontakt udmelding. Det viser også, hvordan man sammenligner to enumkonstanter for at bestemme, hvilken konstant der kommer før den anden konstant.

Liste 1: TEDemo.java (version 1)

offentlig klasse TEDemo {enum Retning {NORTH, WEST, EAST, SOUTH} offentlig statisk ugyldig hoved (String [] args) {for (int i = 0; i <Retning.værdier (). længde; i ++) {Retning d = Retning .værdier () [i]; System.out.println (d); switch (d) {case NORTH: System.out.println ("Flyt nord"); pause; sag WEST: System.out.println ("Flyt vest"); pause; sag ØST: System.out.println ("Flyt øst"); pause; sag SYD: System.out.println ("Flyt syd"); pause; standard: hævde falsk: "ukendt retning"; }} System.out.println (Direction.NORTH.compareTo (Direction.SOUTH)); }}

Liste 1 erklærer Retning typesafe enum og gentages over dets konstante medlemmer, som værdier () vender tilbage. For hver værdi er kontakt erklæring (forbedret til at understøtte typesafe enums) vælger sag der svarer til værdien afd og udsender en passende meddelelse. (Du har ikke forud for en enumkonstant, f.eks. NORD, med sin enumtype.) Endelig evaluerer Listing 1 Retning.NORTH.compareTo (Retning.SØD) for at afgøre, om NORD kommer før SYD.

Kompilér kildekoden som følger:

javac TEDemo.java

Kør den kompilerede applikation som følger:

java TEDemo

Du skal overholde følgende output:

NORTH Flyt nord VEST Flyt vest ØST Flyt øst SYD Flyt syd -3

Outputtet afslører, at den arvede toString () metode returnerer navnet på enumskonstanten, og det NORD kommer før SYD i en sammenligning af disse enumkonstanter.

Tilføjelse af data og adfærd til en typesafe enum

Du kan tilføje data (i form af felter) og adfærd (i form af metoder) til en typesafe enum. Antag for eksempel, at du har brug for at indføre et enum for canadiske mønter, og at denne klasse skal give midlerne til at returnere antallet af nikkel, dimes, kvartaler eller dollars indeholdt i et vilkårligt antal øre. Liste 2 viser dig, hvordan du udfører denne opgave.

Liste 2: TEDemo.java (version 2)

enum Coin {NICKEL (5), // konstanter skal vises første DIME (10), QUARTER (25), DOLLAR (100); // semikolon er påkrævet privat endelig int værdiInPennies; Mønt (int valueInPennies) {this.valueInPennies = valueInPennies; } int toCoins (int pennies) {return pennies / valueInPennies; }} public class TEDemo {public static void main (String [] args) {if (args.length! = 1) {System.err.println ("use: java TEDemo amountInPennies"); Vend tilbage; } int pennies = Integer.parseInt (args [0]); for (int i = 0; i <Coin.values ​​(). længde; i ++) System.out.println (pennies + "pennies indeholder" + Coin.values ​​() [i] .toCoins (pennies) + "" + Coin .værdier () [i] .toString (). toLowerCase () + "s"); }}

Liste 2 erklærer først a Mønt enum. En liste over parametriserede konstanter identificerer fire slags mønter. Argumentet, der sendes til hver konstant, repræsenterer antallet af øre, som mønten repræsenterer.

Argumentet, der sendes til hver konstant, overføres faktisk til Mønt (int værdiInPennies) konstruktør, som gemmer argumentet i valuesInPennies eksempelfelt. Der er adgang til denne variabel inden for toCoins () eksempel metode. Det opdeles i antallet af øre, der sendes til toCoin ()'S øre parameter, og denne metode returnerer resultatet, hvilket tilfældigvis er antallet af mønter i den monetære betegnelse beskrevet af Mønt konstant.

På dette tidspunkt har du opdaget, at du kan erklære forekomstfelter, konstruktører og instansmetoder i en typesafe enum. Når alt kommer til alt er en typesafe enum i det væsentlige en særlig slags Java-klasse.

Det TEDemo klasse hoved () metode verificerer først, at der er angivet et enkelt kommandolinjeargument. Dette argument konverteres til et heltal ved at kalde java.lang. heltal klasse parseInt () metode, der analyserer værdien af ​​dens strengargument i et heltal (eller kaster en undtagelse, når ugyldig input detekteres). Jeg har mere at sige om Heltal og dets fætterklasser i fremtiden Java 101 artikel.

Bevæger sig fremad, hoved () itererer over MøntS konstanter. Fordi disse konstanter opbevares i en Mønt[] matrix, hoved () evaluerer Møntværdier (). Længde for at bestemme længden af ​​denne matrix. For hver iteration af loop-indeks jeg, hoved () evaluerer Møntværdier () [i] for at få adgang til Mønt konstant. Det påberåber hver af dem toCoins () og toString () på denne konstante, hvilket yderligere beviser det Mønt er en særlig slags klasse.

Kompilér kildekoden som følger:

javac TEDemo.java

Kør den kompilerede applikation som følger:

java TEDemo 198

Du skal overholde følgende output:

198 øre indeholder 39 nikkel 198 øre indeholder 19 dimes 198 øre indeholder 7 kvartaler 198 øre indeholder 1 dollars

Udforskning af Enum klasse

Java-kompilatoren overvejer enum at være syntaktisk sukker. Ved stød på en typesafe enum-erklæring genererer den en klasse, hvis navn er angivet af erklæringen. Denne klasse underklasser det abstrakte Enum klasse, der fungerer som basisklasse for alle typesafe enums.

Enum'S liste over formelle parametre af typen ser uhyggelig ud, men det er ikke så svært at forstå. For eksempel i sammenhæng med Mønt strækker sig Enum, ville du fortolke denne formelle liste over parametre som følger:

  • Enhver underklasse af Enum skal levere et faktisk argument til Enum. For eksempel, MøntHeader specificerer Enum.
  • Det aktuelle argumentargument skal være en underklasse af Enum. For eksempel, Mønt er en underklasse af Enum.
  • En underklasse af Enum (såsom Mønt) skal følge idiomet om, at det leverer sit eget navn (Mønt) som et faktisk argument.

Undersøge EnumJava-dokumentation, og du vil opdage, at den tilsidesætter java.lang.Objekt's klon (), lige med(), færdiggør (), hashCode ()og toString () metoder. Med undtagelse af toString (), erklæres alle disse overordnede metoder endelig så de ikke kan tilsidesættes i en underklasse:

  • klon () er tilsidesat for at forhindre konstanter i at blive klonet, så der aldrig er mere end en kopi af en konstant; ellers kunne konstanter ikke sammenlignes via == og !=.
  • lige med() er tilsidesat for at sammenligne konstanter via deres referencer. Konstanter med de samme identiteter (==) skal have samme indhold (lige med()), og forskellige identiteter antyder forskelligt indhold.
  • færdiggør () er tilsidesat for at sikre, at konstanter ikke kan færdiggøres.
  • hashCode () er tilsidesat fordi lige med() er tilsidesat.
  • toString () er tilsidesat for at returnere konstantens navn.

Enum giver også sine egne metoder. Disse metoder inkluderer endeligsammenligne med() (Enum implementerer java.lang. sammenlignelig interface), getDeclaringClass (), navn()og ordinal () metoder: