Programmering

Hvordan bruke typesafe enums i Java

Java-kode som bruker tradisjonelle nummererte typer er problematisk. Java 5 ga oss et bedre alternativ i form av typesafe enums. I denne artikkelen introduserer jeg deg for opplistede typer og typesafe enums, viser deg hvordan du erklærer et typesafe enum og bruker det i en switch-uttalelse, og diskuterer tilpasning av typesafe enum ved å legge til data og atferd. Jeg avslutter artikkelen ved å utforske java.lang.Enum klasse.

last ned Få koden Last ned kildekoden for eksempler i denne Java 101-opplæringen. Skapt av Jeff Friesen for JavaWorld /.

Fra oppregnede typer til typesafe enums

An oppregnet type spesifiserer et sett med relaterte konstanter som verdier. Eksempler inkluderer en uke med dager, standard kompassretningene nord / sør / øst / vest, myntbetegnelser for en valuta og leksikalske analysatorens token-typer.

Oppregnede typer har tradisjonelt blitt implementert som sekvenser av heltallskonstanter, noe som demonstreres av følgende sett med retningskonstanter:

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

Det er flere problemer med denne tilnærmingen:

  • Mangel på typesikkerhet: Fordi en nummerert typekonstant bare er et helt tall, kan hvilket som helst heltall spesifiseres der konstanten er nødvendig. Videre kan tillegg, subtraksjon og andre matteoperasjoner utføres på disse konstantene; for eksempel, (DIR_NORTH + DIR_EAST) / DIR_SOUTH), som er meningsløs.
  • Navneområdet er ikke til stede: En oppregnet type konstanter må være foran en slags (forhåpentligvis) unik identifikator (f.eks. DIR_) for å forhindre kollisjoner med konstanter fra en annen oppført type.
  • Skørhet: Fordi oppregnede typekonstanter er samlet i klassefiler der deres bokstavelige verdier er lagret (i konstante bassenger), krever endring av en konstants verdi at disse klassefilene og de programklassefilene som er avhengige av dem, gjenoppbygges. Ellers vil udefinert oppførsel oppstå ved kjøretid.
  • Mangel på informasjon: Når en konstant skrives ut, blir dens heltallverdi matet ut. Denne utgangen forteller deg ingenting om hva heltallet representerer. Det identifiserer ikke engang den oppregnede typen som konstanten tilhører.

Du kan unngå “mangel på typesikkerhet” og “mangel på informasjon” ved å bruke java.lang.Streng konstanter. For eksempel kan du spesifisere statisk slutt String DIR_NORTH = "NORTH";. Selv om den konstante verdien er mer meningsfull, String-baserte konstanter lider fortsatt av problemer med "navneområdet ikke er til stede" og sprøhet. I motsetning til sammenligninger med heltall kan du ikke sammenligne strengverdier med == og != operatører (som bare sammenligner referanser).

Disse problemene førte til at utviklere oppfant et klassebasert alternativ kjent som Typesafe Enum. Dette mønsteret har blitt mye beskrevet og kritisert. Joshua Bloch introduserte mønsteret i Item 21 av hans Effektiv Java-programmeringsspråkguide (Addison-Wesley, 2001) og bemerket at det har noen problemer; nemlig at det er vanskelig å samle typesafe enum-konstanter i sett, og at oppregningskonstanter ikke kan brukes i bytte om uttalelser.

Tenk på følgende eksempel på typesafe enum mønster. De Dress klasse viser hvordan du kan bruke det klassebaserte alternativet til å introdusere en opptalt type som beskriver de fire kortdraktene (klubber, diamanter, hjerter og spar):

offentlig finaleklasse Drakt // Skal ikke kunne underklasse Drakt. {offentlig statisk sluttdrakt CLUBS = ny drakt (); offentlig statisk finale Suit DIAMONDS = new Suit (); offentlig statisk slutt Suit HEARTS = new Suit (); offentlig statisk finale Suit SPADES = new Suit (); private Suit () {} // Bør ikke kunne introdusere flere konstanter. }

For å bruke denne klassen vil du introdusere en Dress variabel og tilordne den til en av DressKonstanter, som følger:

Dress dress = Suit.DIAMONDS;

Det kan være lurt å avhøre dress i en bytte om uttalelse som denne:

switch (dress) {case Suit.CLUBS: System.out.println ("klubber"); gå i stykker; sak Suit.DIAMONDS: System.out.println ("diamanter"); gå i stykker; sak Suit.HEARTS: System.out.println ("hjerter"); gå i stykker; sak Suit.SPADES: System.out.println ("spader"); }

Imidlertid når Java-kompilatoren møter Dress.CLUBS, rapporterer det en feil som sier at det kreves et konstant uttrykk. Du kan prøve å løse problemet på følgende måte:

switch (dress) {case CLUBS: System.out.println ("klubber"); gå i stykker; sak DIAMANTER: System.out.println ("diamanter"); gå i stykker; sak HJERTER: System.out.println ("hjerter"); gå i stykker; sak SPADES: System.out.println ("spades"); }

Imidlertid når kompilatoren møter KLUBBER, vil den rapportere en feil som sier at den ikke kunne finne symbolet. Og selv om du plasserte Dress i en pakke, importerte pakken og statisk importerte disse konstantene, ville kompilatoren klage over at den ikke kan konvertere Dress til int når du møter dress i bryter (dress). Når det gjelder hver sak, vil kompilatoren også rapportere at det kreves et konstant uttrykk.

Java støtter ikke Typesafe Enum-mønsteret med bytte om uttalelser. Imidlertid introduserte den typesafe enum språkfunksjon for å kapsle fordelene til mønsteret mens du løser problemene, og denne funksjonen støtter ikke bytte om.

Erklære en typesafe enum og bruke den i en brytererklæring

En enkel typesafe enum-deklarasjon i Java-kode ser ut som motstykkene i språkene C, C ++ og C #:

enum Direction {NORTH, WEST, EAST, SOUTH}

Denne erklæringen bruker nøkkelordet enum å introdusere Retning som en typesafe enum (en spesiell type klasse), hvor vilkårlige metoder kan legges til og vilkårlige grensesnitt kan implementeres. De NORD, VEST, ØST, og SØRenum konstanter blir implementert som konstant-spesifikke klasseorganer som definerer anonyme klasser som utvider det omsluttende Retning klasse.

Retning og andre typesafe enums utvides Enum og arve ulike metoder, inkludert verdier (), toString (), og sammenligne med(), fra denne klassen. Vi skal utforske Enum senere i denne artikkelen.

Oppføring 1 erklærer ovennevnte enum og bruker den i a bytte om uttalelse. Den viser også hvordan man sammenligner to enumkonstanter for å bestemme hvilken konstant som kommer før den andre konstanten.

Oppføring 1: TEDemo.java (versjon 1)

public class TEDemo {enum Direction {NORTH, WEST, EAST, SOUTH} public static void main (String [] args) {for (int i = 0; i <Direction.values ​​(). length; i ++) {Direction d = Direction .verdier () [i]; System.out.println (d); bryter (d) {case NORTH: System.out.println ("Flytt nordover"); gå i stykker; sak WEST: System.out.println ("Flytt vest"); gå i stykker; sak ØST: System.out.println ("Flytt øst"); gå i stykker; sak SYD: System.out.println ("Flytt sørover"); gå i stykker; standard: assert false: "ukjent retning"; }} System.out.println (Direction.NORTH.compareTo (Direction.SOUTH)); }}

Oppføring 1 erklærer Retning typesafe enum og iterates over sine konstante medlemmer, som verdier () returnerer. For hver verdi er bytte om uttalelse (forbedret for å støtte typesafe enums) velger sak som tilsvarer verdien avd og sender ut en passende melding. (Du har ikke foran en enumkonstant, f.eks. NORD, med sin enumtype.) Til slutt vurderes oppføring 1 Direction.NORTH.compareTo (Direction.SOUTH) for å avgjøre om NORD kommer før SØR.

Kompiler kildekoden som følger:

javac TEDemo.java

Kjør den kompilerte applikasjonen som følger:

java TEDemo

Du bør følge følgende utdata:

NORD Flytt nord VEST Flytt vest ØST Flytt øst SØR Flytt sørover -3

Resultatet avslører at arvet toString () metoden returnerer navnet på enumkonstanten, og det NORD kommer før SØR i en sammenligning av disse enumkonstantene.

Legge til data og atferd i en typesafe enum

Du kan legge til data (i form av felt) og atferd (i form av metoder) til en typesafe enum. Anta for eksempel at du trenger å innføre et enum for kanadiske mynter, og at denne klassen må gi midler til å returnere antall nikkel, dimes, kvartaler eller dollar som er inneholdt i et vilkårlig antall øre. Oppføring 2 viser deg hvordan du kan utføre denne oppgaven.

Oppføring 2: TEDemo.java (versjon 2)

enum Coin {NICKEL (5), // constants must appear first DIME (10), QUARTER (25), DOLLAR (100); // semikolonet kreves privat endelig int valueInPennies; Mynt (int valueInPennies) {this.valueInPennies = valueInPennies; } int toCoins (int pennies) {return pennies / valueInPennies; }} offentlig klasse TEDemo {offentlig statisk tomrom hoved (String [] args) {if (args.length! = 1) {System.err.println ("bruk: java TEDemo amountInPennies"); komme tilbake; } int pennies = Integer.parseInt (args [0]); for (int i = 0; i <Coin.values ​​(). lengde; i ++) System.out.println (pennies + "pennies inneholder" + Coin.values ​​() [i] .toCoins (pennies) + "" + Coin .verdier () [i] .toString (). toLowerCase () + "s"); }}

Oppføring 2 erklærer først a Mynt enum. En liste over parametriserte konstanter identifiserer fire typer mynter. Argumentet som sendes til hver konstant representerer antall øre som mynten representerer.

Argumentet som sendes til hver konstant blir faktisk sendt til Mynt (int valueInPennies) konstruktør, som lagrer argumentet i valuesInPennies forekomstfelt. Denne variabelen er tilgjengelig fra toCoins () eksempel metode. Den deler seg inn i antall øre som sendes til toCoin ()’S øre parameter, og denne metoden returnerer resultatet, som tilfeldigvis er antall mynter i pengesamfunnet som er beskrevet av Mynt konstant.

På dette tidspunktet har du oppdaget at du kan erklære forekomstfelt, konstruktører og forekomstmetoder i en typesafe enum. Tross alt er en typesafe enum egentlig en spesiell type Java-klasse.

De TEDemo klasse’s hoved() metoden verifiserer først at et enkelt kommandolinjeargument er spesifisert. Dette argumentet konverteres til et helt tall ved å kalle java.lang. heltall klasse’s parseInt () metode, som analyserer verdien av strengargumentet til et helt tall (eller kaster et unntak når ugyldig inndata blir oppdaget). Jeg får mer å si om Heltall og fetterklassene i fremtiden Java 101 artikkel.

Går videre, hoved() itererer over MyntSine konstanter. Fordi disse konstantene er lagret i en Mynt[] matrise, hoved() evaluerer Myntverdier (). Lengde for å bestemme lengden på denne matrisen. For hver iterasjon av løkkeindeks Jeg, hoved() evaluerer Coin.values ​​() [i] for å få tilgang til Mynt konstant. Den påkaller hver av dem toCoins () og toString () på denne konstanten, noe som ytterligere beviser det Mynt er en spesiell type klasse.

Kompiler kildekoden som følger:

javac TEDemo.java

Kjør den kompilerte applikasjonen som følger:

java TEDemo 198

Du bør følge følgende utdata:

198 pennies inneholder 39 nikkel 198 pennies inneholder 19 dimes 198 pennies inneholder 7 kvartaler 198 pennies inneholder 1 dollar

Utforske Enum klasse

Java-kompilatoren vurderer enum å være syntaktisk sukker. Når det møter en typesafe enum-erklæring, genererer den en klasse hvis navn er spesifisert av erklæringen. Denne klassen underklasser det abstrakte Enum klasse, som fungerer som basisklasse for alle typesafe enums.

EnumDen formelle parameterlisten for typen ser uhyggelig ut, men den er ikke så vanskelig å forstå. For eksempel i sammenheng med Mynt utvider Enum, vil du tolke denne formelle parameterlisten som følger:

  • Enhver underklasse av Enum må levere et faktisk argument for Enum. For eksempel, MyntHeader spesifiserer Enum.
  • Selve typeargumentet må være en underklasse av Enum. For eksempel, Mynt er en underklasse av Enum.
  • En underklasse av Enum (som for eksempel Mynt) må følge uttrykket at det oppgir sitt eget navn (Mynt) som et faktisk typeargument.

Undersøke EnumJava-dokumentasjon, og du vil oppdage at den overstyrer java.lang.Objekts klone (), er lik(), fullføre (), hashCode (), og toString () metoder. Utenom toString (), blir alle disse overordnede metodene erklært endelig slik at de ikke kan overstyres i en underklasse:

  • klone () overstyres for å forhindre at konstanter blir klonet slik at det aldri er mer enn en kopi av en konstant; ellers kunne ikke konstanter sammenlignes via == og !=.
  • er lik() er overstyrt for å sammenligne konstanter via deres referanser. Konstanter med samme identitet (==) må ha samme innhold (er lik()), og forskjellige identiteter innebærer forskjellig innhold.
  • fullføre () overstyres for å sikre at konstanter ikke kan fullføres.
  • hashCode () blir overstyrt fordi er lik() overstyres.
  • toString () overstyres for å returnere konstantens navn.

Enum gir også sine egne metoder. Disse metodene inkluderer endeligsammenligne med() (Enum implementerer java.lang. sammenlignbar grensesnitt), getDeclaringClass (), Navn(), og ordinær () metoder:

$config[zx-auto] not found$config[zx-overlay] not found