Programmering

Opret opregnede konstanter i Java

Et sæt "utallige konstanter" er en ordnet samling af konstanter, der kan tælles, som tal. Denne egenskab giver dig mulighed for at bruge dem som tal til at indeksere en matrix, eller du kan bruge dem som indeksvariablen i en for-loop. I Java er sådanne objekter oftest kendt som "opregnede konstanter."

Brug af opregnede konstanter kan gøre koden mere læsbar. For eksempel vil du muligvis definere en ny datatype med navnet Color med konstanter RØD, GRØN og BLÅ som mulige værdier. Ideen er at have farve som attribut for andre objekter, du opretter, såsom bilobjekter:

 klasse bil {farve farve; ...} 

Derefter kan du skrive en klar, læsbar kode som denne:

 myCar.color = RØD; 

i stedet for noget som:

 myCar.color = 3; 

En endnu vigtigere egenskab af opregnede konstanter på sprog som Pascal er, at de er typesikre. Med andre ord er det ikke muligt at tildele en ugyldig farve til farveattributten - den skal altid være enten RØD, GRØN eller BLÅ. I modsætning hertil, hvis farvevariablen var en int, så kunne du tildele et hvilket som helst gyldigt heltal til det, selvom dette tal ikke repræsenterede en gyldig farve.

Denne artikel giver dig en skabelon til oprettelse af opregnede konstanter, der er:

  • Skriv sikkert
  • Printbar
  • Bestilt til brug som et indeks
  • Forbundet, til looping fremad eller bagud
  • Tælles

I en fremtidig artikel vil du lære at udvide opregnede konstanter til at implementere statsafhængig adfærd.

Hvorfor ikke bruge statiske finaler?

En almindelig mekanisme for opregnede konstanter bruger statiske endelige int-variabler, som denne:

 statisk endelig int RØD = 0; statisk endelig int GRØNN = 1; statisk endelig int BLÅ = 2; ... 

Statiske finaler er nyttige

Fordi de er endelige, er værdierne konstante og uforanderlige. Fordi de er statiske, oprettes de kun en gang for den klasse eller grænseflade, hvori de er defineret, i stedet for en gang for hvert objekt. Og fordi de er heltalsvariabler, kan de tælles og bruges som et indeks.

For eksempel kan du skrive en løkke for at oprette en liste over en kundes yndlingsfarver:

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

Du kan også indeksere i en matrix eller en vektor ved hjælp af variablerne for at få en værdi tilknyttet farven. Antag for eksempel, at du har et brætspil, der har forskellige farvede brikker til hver spiller. Lad os sige, at du har en bitmap for hvert farvestykke og en metode, der kaldes Skærm() der kopierer den bitmap til den aktuelle placering. En måde at sætte et stykke på tavlen kan være sådan:

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

void placePiece (int placering, int farve) {setPosition (placering); hvis (farve == RØD) {display (redPiece); } ellers hvis (color == GREEN) {display (greenPiece); } andet {display (bluePiece); }}

Men ved at bruge heltalets værdier til at indeksere i en række stykker, kan du forenkle koden til:

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

At være i stand til at løkke over en række konstanter og indeksere i en matrix eller vektor er de største fordele ved statiske endelige heltal. Og når antallet af valg vokser, er forenklingseffekten endnu større.

Men statiske finaler er risikable

Der er stadig et par ulemper ved at bruge statiske endelige heltal. Den største ulempe er manglen på typesikkerhed. Ethvert heltal, der beregnes eller læses ind, kan bruges som en "farve", uanset om det giver mening at gøre det. Du kan løkke lige forbi slutningen af ​​de definerede konstanter eller stoppe kort for at dække dem alle, hvilket let kan ske, hvis du tilføjer eller fjerner en konstant fra listen, men glemmer at justere løkkeindekset.

For eksempel kan din farveindstillingssløjfe læse sådan:

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

Senere kan du tilføje en ny farve:

 statisk endelig int RØD = 0; statisk endelig int GRØNN = 1; statisk endelig int BLÅ = 2; statisk endelig int MAGENTA = 3; 

Eller du kan fjerne en:

 statisk endelig int RØD = 0; statisk endelig int BLÅ = 1; 

I begge tilfælde fungerer programmet ikke korrekt. Hvis du fjerner en farve, får du en runtime-fejl, der gør opmærksom på problemet. Hvis du tilføjer en farve, får du slet ingen fejl - programmet dækker simpelthen ikke alle farvevalgene.

En anden ulempe er manglen på en læsbar identifikator. Hvis du bruger en meddelelsesboks eller konsoloutput til at vise det aktuelle farvevalg, får du et nummer. Det gør fejlretning ret vanskeligt.

Problemerne med at skabe en læsbar identifikator løses undertiden ved hjælp af statiske endelige strengkonstanter, som denne:

 statisk endelig String RED = "rød" .intern (); ... 

Bruger praktikant () metoden garanterer, at der kun er én streng med dette indhold i den interne strengpool. Men for praktikant () for at være effektiv skal hver streng eller strengvariabel, der nogensinde sammenlignes med RØD, bruge den. Selv da tillader statiske endelige strenge ikke looping eller indeksering i en matrix, og de adresserer stadig ikke spørgsmålet om typesikkerhed.

Type sikkerhed

Problemet med statiske endelige heltal er, at de variabler, der bruger dem, iboende er ubegrænsede. De er int-variabler, hvilket betyder, at de kan rumme ethvert heltal, ikke kun de konstanter, de var beregnet til at holde. Målet er at definere en variabel af typen Color, så du får en kompileringsfejl snarere end en runtime-fejl, hver gang en ugyldig værdi tildeles variablen.

En elegant løsning blev leveret i Philip Bishops artikel i JavaWorld, "Typesafe-konstanter i C ++ og Java."

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

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

offentlig statisk endelig Farve RØD = ny farve (); offentlig statisk endelig Farve GRØN = ny farve (); offentlig statisk endelig Farve BLÅ = ny farve (); }

Da klassen er defineret som endelig, kan den ikke underklasseres. Ingen andre klasser oprettes ud fra det. Da konstruktøren er privat, kan andre metoder ikke bruge klassen til at oprette nye objekter. De eneste objekter, der nogensinde vil blive oprettet med denne klasse, er de statiske objekter, som klassen opretter for sig selv første gang, der refereres til klassen! Denne implementering er en variation af Singleton-mønsteret, der begrænser klassen til et foruddefineret antal forekomster. Du kan bruge dette mønster til at oprette nøjagtigt en klasse, hver gang du har brug for en Singleton, eller bruge det som vist her til at oprette et fast antal forekomster. (Singleton-mønsteret er defineret i bogen Designmønstre: Elementer af genanvendelig objektorienteret software af Gamma, Helm, Johnson og Vlissides, Addison-Wesley, 1995. Se afsnittet Ressourcer for et link til denne bog.)

Den forbløffende del af denne klassedefinition er, at klassen bruger sig selv at skabe nye objekter. Første gang du refererer til RØD, findes den ikke. Men handlingen med at få adgang til den klasse, som RØD er defineret i, får den til at blive skabt sammen med de andre konstanter. Ganske vist er den slags rekursive reference ret vanskelig at visualisere. Men fordelen er total typesikkerhed. En variabel af typen Farve kan aldrig tildeles andet end de RØDE, GRØNNE eller BLÅE objekter, som Farve klasse skaber.

Identifikatorer

Den første forbedring af typesafe-opregnede konstante klasse er at skabe en strengrepræsentation af konstanterne. Du vil være i stand til at producere en læsbar version af værdien med en linje som denne:

 System.out.println (myColor); 

Når du udsender et objekt til en karakterudgangsstrøm som System.out, og når du sammenkæder et objekt til en streng, påberåber Java automatisk toString () metode til det objekt. Det er en god grund til at definere en toString () metode til enhver ny klasse, du opretter.

Hvis klassen ikke har en toString () metode, undersøges arvshierarkiet, indtil en findes. Øverst i hierarkiet vises toString () metode i Objekt klasse returnerer klassens navn. Så toString () metoden altid har gjort nogle betyder, men det meste af tiden vil standardmetoden ikke være særlig nyttig.

Her er en ændring af Farve klasse, der giver en nyttig toString () metode:

offentlig endelig klasse Farve { privat streng-id; privat farve (Streng anID) {this.id = anID; } public String toString () {returner this.id; }

offentlig statisk endelig Farve RØD = ny farve (

"Rød"

); offentlig statisk endelig Farve GRØN = ny farve (

"Grøn"

); offentlig statisk endelig Farve BLÅ = ny farve (

"Blå"

); }

Denne version tilføjer en privat strengvariabel (id). Konstruktøren er blevet ændret til at tage et String-argument og gemme det som objektets ID. Det toString () metode returnerer derefter objektets ID.

Et trick, du kan bruge til at påkalde toString () metode udnytter det faktum, at den automatisk påberåbes, når et objekt sammenkædes til en streng. Det betyder, at du kan placere objektets navn i en dialog ved at sammenkæde det til en nullstreng ved hjælp af en linje som følger:

 textField1.setText ("" + myColor); 

Medmindre du tilfældigvis elsker alle parenteser i Lisp, vil du finde det lidt mere læsbart end alternativet:

 textField1.setText (myColor.toString ()); 

Det er også lettere at sikre, at du sætter det rigtige antal parenteser i lukningen!

Bestilling og indeksering

Det næste spørgsmål er, hvordan man indekserer i en vektor eller et array ved hjælp af medlemmer af

Farve

klasse. Mekanismen vil være at tildele et ordinalt tal til hver klassekonstant og henvise til det ved hjælp af attributten

.ord

, sådan her:

 void placePiece (int placering, int farve) {setPosition (placering); display (stykke [farve.ord]); } 

Selvom tackle på .ord at konvertere henvisningen til farve ind i et nummer er ikke særlig smuk, det er heller ikke meget påtrængende. Det virker som en forholdsvis rimelig afvejning af typesafe-konstanter.

Sådan tildeles de ordinære numre:

offentlig endelig klasse Farve {privat streng-id; offentlig endelig ordre;privat statisk int upperBound = 0; privat farve (String anID) {this.id = anID; this.ord = upperBound ++; } offentlig String toString () {returner this.id; } offentlig statisk int-størrelse () {return upperBound; }

offentlig statisk endelig farve RØD = ny farve ("rød"); offentlig statisk endelig Farve GRØN = ny farve ("grøn"); offentlig statisk endelig Farve BLÅ = ny farve ("Blå"); }

Denne kode bruger den nye JDK version 1.1 definition af en "blank endelig" variabel - en variabel, der tildeles en værdi en gang og en gang. Denne mekanisme tillader hvert objekt at have sin egen ikke-statiske endelige variabel, ord, som tildeles en gang under oprettelse af objekt, og som derefter vil forblive uforanderlige. Den statiske variabel øvre grænse holder styr på det næste ubrugte indeks i samlingen. Denne værdi bliver ord attribut, når objektet oprettes, hvorefter den øvre grænse øges.

For kompatibilitet med Vektor klasse, metoden størrelse() er defineret for at returnere antallet af konstanter, der er defineret i denne klasse (som er den samme som den øvre grænse).

En purist beslutter muligvis, at variablen ord skal være privat, og metoden er navngivet ord () skal returnere det - hvis ikke, en metode navngivet getOrd (). Jeg læner mig mod at få adgang til attributten direkte, dog af to grunde. Den første er, at begrebet en ordinal er utvetydigt et int. Der er ringe sandsynlighed, hvis nogen, for at implementeringen nogensinde vil ændre sig. Den anden grund er, at hvad du virkelig vil have er evnen til at bruge objektet som om det var et int, som du kunne på et sprog som Pascal. For eksempel vil du muligvis bruge attributten farve for at indeksere en matrix. Men du kan ikke bruge et Java-objekt til at gøre det direkte. Hvad du virkelig gerne vil sige er:

 display (stykke [farve]); // ønskeligt, men fungerer ikke 

Men du kan ikke gøre det. Den mindste ændring, der er nødvendig for at få det, du ønsker, er at få adgang til en attribut i stedet som denne:

 display (stykke [color.ord]); // tættest på ønskeligt 

i stedet for det lange alternativ:

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

eller det endnu længerevarende:

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

Eiffelsproget bruger den samme syntaks til at få adgang til attributter og påberåbe sig metoder. Det ville være det ideelle. I betragtning af nødvendigheden af ​​at vælge den ene eller den anden er jeg dog gået med adgang ord som en attribut. Med held, identifikatoren ord bliver så velkendt som et resultat af gentagelse, at brugen af ​​det virker lige så naturligt som at skrive int. (Så naturligt som det måtte være.)

Looping

Det næste trin er at kunne gentage klassekonstanterne. Du vil være i stand til at løkke fra start til slut:

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

eller fra slutningen tilbage til begyndelsen:

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

Disse ændringer bruger statiske variabler til at holde styr på det sidst oprettede objekt og knytte det til det næste objekt: