Programmering

Lag oppregnede konstanter i Java

Et sett med "enumerable konstants" er en ordnet samling av konstanter som kan telles, som tall. Denne egenskapen lar deg bruke dem som tall for å indeksere en matrise, eller du kan bruke dem som indeksvariabelen i en for-loop. I Java er slike gjenstander ofte kjent som "oppregnede konstanter."

Bruk av oppregnede konstanter kan gjøre koden mer lesbar. Det kan for eksempel være lurt å definere en ny datatype med navnet Color med konstantene RØD, GRØNN og BLÅ som mulige verdier. Ideen er å ha farge som attributt til andre objekter du lager, for eksempel bilobjekter:

 klasse Bil {Farge farge; ...} 

Deretter kan du skrive klar, lesbar kode, slik:

 myCar.color = RØD; 

i stedet for noe sånt som:

 myCar.color = 3; 

En enda viktigere egenskap av oppregnede konstanter på språk som Pascal er at de er typesikre. Med andre ord er det ikke mulig å tilordne en ugyldig farge til fargeattributtet - den må alltid være enten RØD, GRØNN eller BLÅ. Derimot, hvis fargevariabelen var en int, kan du tildele et hvilket som helst gyldig heltall til den, selv om dette tallet ikke representerte en gyldig farge.

Denne artikkelen gir deg en mal for å lage oppregnede konstanter som er:

  • Skriv trygt
  • Utskriftsvennlig
  • Bestilt, til bruk som indeks
  • Koblet, for å løkke fremover eller bakover
  • Utallige

I en fremtidig artikkel vil du lære hvordan du utvider oppregnede konstanter til å implementere statsavhengig oppførsel.

Hvorfor ikke bruke statiske finaler?

En vanlig mekanisme for oppregnede konstanter bruker statiske endelige int-variabler, slik:

 statisk slutt int RED = 0; statisk slutt int GRØNN = 1; statisk slutt int BLÅ = 2; ... 

Statisk finale er nyttig

Fordi de er endelige, er verdiene konstante og uforanderlige. Fordi de er statiske, blir de bare opprettet en gang for klassen eller grensesnittet der de er definert, i stedet for en gang for hvert objekt. Og fordi de er heltallvariabler, kan de telles opp og brukes som en indeks.

For eksempel kan du skrive en løkke for å lage en liste over kundens favorittfarger:

 for (int i = 0; ...) {if (customerLikesColor (i)) {favoriteColors.add (i); }} 

Du kan også indeksere i en matrise eller en vektor ved hjelp av variablene for å få en verdi tilknyttet fargen. Anta for eksempel at du har et brettspill som har forskjellige fargede brikker for hver spiller. La oss si at du har en bitmappe for hvert fargestykke og en metode som kalles vise() som kopierer den bitmappen til gjeldende plassering. En måte å sette et stykke på tavlen kan være noe av dette:

PiecePicture redPiece = ny PiecePicture (RØD); PiecePicture greenPiece = ny PiecePicture (GRØNN); PiecePicture bluePiece = ny PiecePicture (BLÅ);

void placePiece (int location, int color) {setPosition (location); hvis (color == RED) {display (redPiece); } annet hvis (color == GREEN) {display (greenPiece); } annet {display (bluePiece); }}

Men ved å bruke heltallverdiene til å indeksere i en rekke stykker, kan du forenkle koden til:

 PiecePicture [] piece = {new PiecePicture (RED), new PiecePicture (GREEN), new PiecePicture (BLUE)}; void placePiece (int location, int color) {setPosition (location); display (stykke [farge]); } 

Å være i stand til å løkke over en rekke konstanter og indeksere i en matrise eller vektor er de største fordelene med statiske endelige heltall. Og når antallet valg øker, er forenklingseffekten enda større.

Men statiske finaler er risikabelt

Likevel er det et par ulemper ved å bruke statiske endelige heltall. Den største ulempen er mangelen på typesikkerhet. Ethvert heltall som beregnes eller leses inn kan brukes som en "farge", uansett om det er fornuftig å gjøre det. Du kan sløyfe rett forbi slutten av de definerte konstantene eller stoppe for å dekke dem alle, noe som lett kan skje hvis du legger til eller fjerner en konstant fra listen, men glemmer å justere sløyfeindeksen.

For eksempel kan fargevalgsløyfen lese slik:

 for (int i = 0; i <= BLÅ; i ++) {if (customerLikesColor (i)) {favoriteColors.add (i); }} 

Senere kan du legge til en ny farge:

 statisk slutt int RED = 0; statisk slutt int GRØNN = 1; statisk slutt int BLÅ = 2; statisk slutt int MAGENTA = 3; 

Eller du kan fjerne en:

 statisk slutt int RED = 0; statisk slutt int BLÅ = 1; 

I begge tilfeller fungerer ikke programmet riktig. Hvis du fjerner en farge, vil du få en kjøretidsfeil som gjør oppmerksom på problemet. Hvis du legger til en farge, får du ingen feil i det hele tatt - programmet vil ganske enkelt ikke dekke alle fargevalgene.

En annen ulempe er mangelen på en lesbar identifikator. Hvis du bruker en meldingsboks eller konsollutgang for å vise gjeldende fargevalg, får du et tall. Det gjør feilsøking ganske vanskelig.

Problemene med å lage en lesbar identifikator løses noen ganger ved hjelp av statiske endelige strengkonstanter, slik:

 statisk sluttstreng RØD = "rød" .intern (); ... 

Bruker turnuskandidat() metoden garanterer at det bare er én streng med innholdet i den interne strengbassenget. Men for turnuskandidat() For å være effektiv, må hver streng eller strengvariabel som noen gang sammenlignes med RØD bruke den. Selv da tillater ikke statiske sluttstrenger looping eller indeksering i en matrise, og de adresserer fortsatt ikke problemet med typesikkerhet.

Type sikkerhet

Problemet med statiske endelige heltall er at variablene som bruker dem iboende er ubegrensede. De er int-variabler, noe som betyr at de kan ha et hvilket som helst heltall, ikke bare konstantene de var ment å holde. Målet er å definere en variabel av typen Color slik at du får en kompileringsfeil i stedet for en kjøretidsfeil når en ugyldig verdi tildeles variabelen.

En elegant løsning ble gitt i Philip Bishops artikkel i JavaWorld, "Typesafe konstanter i C ++ og Java."

Ideen er veldig enkel (når du først ser det!):

offentlig finaleklasse Farge {// finaleklasse !! private Color () {} // private konstruktører !!

offentlig statisk slutt Farge RØD = ny farge (); offentlig statisk slutt Farge GRØNN = ny farge (); offentlig statisk slutt Farge BLÅ = ny farge (); }

Fordi klassen er definert som endelig, kan den ikke underklasseres. Ingen andre klasser vil bli opprettet fra den. Fordi konstruktøren er privat, kan andre metoder ikke bruke klassen til å lage nye objekter. De eneste objektene som noen gang vil bli opprettet med denne klassen er de statiske objektene klassen lager for seg selv første gang det refereres til klassen! Denne implementeringen er en variant av Singleton-mønsteret som begrenser klassen til et forhåndsdefinert antall forekomster. Du kan bruke dette mønsteret til å lage nøyaktig en klasse når du trenger en Singleton, eller bruke den som vist her for å lage et fast antall forekomster. (Singleton-mønsteret er definert i boka Designmønstre: Elementer av gjenbrukbar objektorientert programvare av Gamma, Helm, Johnson og Vlissides, Addison-Wesley, 1995. Se seksjonen Ressurser for en lenke til denne boken.)

Den ufattelige delen av denne klassedefinisjonen er at klassen bruker seg selv for å lage nye objekter. Første gang du refererer til RØD, eksisterer den ikke. Men handlingen med å få tilgang til klassen som RØD er definert i, fører til at den blir opprettet, sammen med de andre konstantene. Riktignok er den typen rekursiv referanse ganske vanskelig å visualisere. Men fordelen er total type sikkerhet. En variabel av typen Color kan aldri tildeles noe annet enn de RØDE, GRØNNE eller BLÅ objektene som Farge klasse skaper.

Identifikatorer

Den første forbedringen av typesafe oppregnet konstant klasse er å lage en strengrepresentasjon av konstantene. Du vil være i stand til å produsere en lesbar versjon av verdien med en linje som denne:

 System.out.println (myColor); 

Hver gang du sender et objekt til en karakterutgangsstrøm som System.out, og når du sammenføyer et objekt til en streng, påkaller Java automatisk toString () metode for det objektet. Det er en god grunn til å definere en toString () metode for enhver ny klasse du oppretter.

Hvis klassen ikke har en toString () metode, blir arvshierarkiet inspisert til en blir funnet. På toppen av hierarkiet, er toString () metoden i Gjenstand klasse returnerer kursnavnet. Så toString () metoden alltid har gjort noen mening, men mesteparten av tiden vil ikke standardmetoden være veldig nyttig.

Her er en modifikasjon av Farge klasse som gir en nyttig toString () metode:

offentlig finaleklasse Farge { privat streng-id; privat farge (Streng anID) {this.id = anID; } public String toString () {return this.id; }

offentlig statisk endelig Farge RØD = ny farge (

"Rød"

); offentlig statisk endelig Farge GRØNN = ny farge (

"Grønn"

); offentlig statisk endelig Farge BLÅ = ny farge (

"Blå"

); }

Denne versjonen legger til en privat strengvariabel (id). Konstruktøren er modifisert for å ta et String-argument og lagre det som objektets ID. De toString () metoden returnerer deretter objektets ID.

Ett triks du kan bruke til å påkalle toString () metoden utnytter det faktum at den automatisk påkalles når et objekt sammenkobles til en streng. Det betyr at du kan sette objektets navn i en dialog ved å sammenkoble det til en nullstreng ved hjelp av en linje som følger:

 textField1.setText ("" + myColor); 

Med mindre du tilfeldigvis elsker alle parentesene i Lisp, vil du finne det litt mer leselig enn alternativet:

 textField1.setText (myColor.toString ()); 

Det er også lettere å sørge for at du setter inn riktig antall parenteser!

Bestilling og indeksering

Det neste spørsmålet er hvordan man indekserer til en vektor eller en matrise ved hjelp av medlemmer av

Farge

klasse. Mekanismen vil være å tilordne et ordinært tall til hver klassekonstant og referere til det ved hjelp av attributtet

.ord

, som dette:

 void placePiece (int location, int color) {setPosition (location); display (stykke [farge.ord]); } 

Selv om du tar tak i det .ord å konvertere referansen til farge inn i et nummer er ikke spesielt pent, det er ikke veldig påtrengende heller. Det virker som en ganske rimelig kompromiss for typesafe-konstanter.

Slik tildeles ordinære tall:

offentlig sluttklasse Farge {privat String id; offentlig endelig int ord;privat statisk int upperBound = 0; private Color (String anID) {this.id = anID; this.ord = upperBound ++; } public String toString () {return this.id; } offentlig statisk int størrelse () {return upperBound; }

offentlig statisk sluttfarge RØD = ny farge ("rød"); offentlig statisk endelig Farge GRØNN = ny farge ("grønn"); offentlig statisk endelig Farge BLÅ = ny farge ("Blå"); }

Denne koden bruker den nye JDK versjon 1.1-definisjonen av en "blank endelig" variabel - en variabel som tildeles en verdi en gang og en gang. Denne mekanismen tillater hvert objekt å ha sin egen ikke-statiske sluttvariabel, ord, som vil bli tildelt en gang under oppretting av objekt, og som deretter vil forbli uforanderlig. Den statiske variabelen øvre grense holder rede på neste ubrukte indeks i samlingen. Denne verdien blir ord attributt når objektet er opprettet, hvoretter den øvre grensen økes.

For kompatibilitet med Vector klasse, metoden størrelse() er definert for å returnere antall konstanter som er definert i denne klassen (som er den samme som den øvre grensen).

En purist kan bestemme at variabelen ord skal være privat, og metoden heter ord () skal returnere den - hvis ikke, en metode som heter getOrd (). Jeg lener meg mot å få tilgang til attributtet direkte, av to grunner. Den første er at begrepet ordinal er utvetydig det int. Det er liten sannsynlighet, hvis noen, for at implementeringen noen gang vil endres. Den andre grunnen er at det du virkelig ønsker er evnen til å bruke objektet som om det var et int, som du kunne på et språk som Pascal. Det kan for eksempel være lurt å bruke attributtet farge for å indeksere en matrise. Men du kan ikke bruke et Java-objekt til å gjøre det direkte. Det du virkelig vil si er:

 display (stykke [farge]); // ønskelig, men fungerer ikke 

Men du kan ikke gjøre det. Den minste endringen som er nødvendig for å få det du vil, er å få tilgang til et attributt, i stedet for slik:

 display (stykke [color.ord]); // nærmest ønskelig 

i stedet for det lange alternativet:

 display (stykke [color.ord ()]); // ekstra parentes 

eller enda lengre:

 display (stykke [color.getOrd ()]); // ekstra parenteser og tekst 

Eiffelspråket bruker den samme syntaksen for å få tilgang til attributter og påkalle metoder. Det ville være det ideelle. Gitt behovet for å velge det ene eller det andre, har jeg imidlertid gått med tilgang ord som attributt. Med hell, identifikatoren ord vil bli så kjent som et resultat av repetisjon at bruk av det vil virke like naturlig som å skrive int. (Så naturlig som det måtte være.)

Looping

Det neste trinnet er å være i stand til å iterere over klassekonstantene. Du vil være i stand til å løkke fra begynnelse til slutt:

 for (Color c = Color.first (); c! = null; c = c.next ()) {...} 

eller fra slutten tilbake til begynnelsen:

 for (Color c = Color.last (); c! = null; c = c.prev ()) {...} 

Disse modifikasjonene bruker statiske variabler for å holde rede på det siste objektet som ble opprettet og koble det til neste objekt:

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