Ohjelmointi

Kuinka käyttää typeafe-keskusteluja Java-ohjelmassa

Java-koodi, joka käyttää perinteisiä lueteltuja tyyppejä, on ongelmallista. Java 5 antoi meille paremman vaihtoehdon tyyppikohtaisten enumien muodossa. Tässä artikkelissa esitän sinulle luetellut tyypit ja tyyppikohtaiset enumit, näytän sinulle, kuinka julistaa tyypin turvallinen enum ja käyttää sitä kytkinlausunnossa, ja keskustelen tyypin turvallisuuden mukauttamisesta lisäämällä tietoja ja käyttäytymistä. Käärin artikkelin tutkimalla java.lang.Enum luokassa.

lataa Hanki koodi Lataa lähdekoodi esimerkkejä tästä Java 101 -oppaasta. Luonut Jeff Friesen JavaWorldille /.

Luetelluista tyypeistä tyyppikohtaisiin enumeihin

An lueteltu tyyppi määrittää joukon toisiinsa liittyviä vakioita arvoina. Esimerkkejä ovat viikonpäivä, tavalliset pohjoisen / etelän / itän / lännen kompassisuunnat, valuutan kolikoiden nimet ja leksikaalisen analysaattorin tunnusmerkit.

Luetteloituja tyyppejä on perinteisesti toteutettu kokonaislukujen vakioina, mikä osoitetaan seuraavilla suuntavakioilla:

staattinen lopullinen int DIR_NORTH = 0; staattinen lopullinen int DIR_WEST = 1; staattinen lopullinen int DIR_EAST = 2; staattinen lopullinen int DIR_SOUTH = 3;

Tässä lähestymistavassa on useita ongelmia:

  • Tyypin turvallisuuden puute: Koska lueteltu tyyppivakio on vain kokonaisluku, mikä tahansa kokonaisluku voidaan määrittää missä vakio vaaditaan. Lisäksi näille vakioille voidaan suorittaa summaus-, vähennys- ja muita matemaattisia operaatioita; esimerkiksi, (DIR_NORTH + DIR_EAST) / DIR_SOUTH), mikä on merkityksetöntä.
  • Nimiavaruutta ei ole: Luetellun tyypin vakiot on etuliitettävä jonkinlaisella (toivottavasti) yksilöllisellä tunnuksella (esim. DIR_) estämään törmäykset toisen luetellun tyypin vakioiden kanssa.
  • Hauraus: Koska luetellut tyypin vakiot kootaan luokkatiedostoihin, joihin niiden kirjaimelliset arvot on tallennettu (vakioalueisiin), vakion arvon muuttaminen edellyttää, että nämä luokkatiedostot ja niistä riippuvat sovellusluokkatiedostot rakennetaan uudelleen. Muussa tapauksessa määrittelemätöntä käyttäytymistä esiintyy ajon aikana.
  • Tiedon puute: Kun vakio tulostetaan, sen kokonaislukuarvo tulostuu. Tämä lähtö ei kerro mitään siitä, mitä kokonaislukuarvo edustaa. Se ei edes tunnista luetteloitua tyyppiä, johon vakio kuuluu.

Voit välttää "tyyppiturvallisuuden puutteen" ja "tiedon puutteen" ongelmat käyttämällä java.lang.String vakiot. Voit esimerkiksi määrittää staattinen lopullinen merkkijono DIR_NORTH = "NORTH";. Vaikka vakioarvo on merkityksellisempi, Merkkijono-pohjaisista vakioista kärsii edelleen ”nimitilaa ei ole” ja haurausongelmat. Toisin kuin kokonaislukuvertailut, et myöskään voi verrata merkkijonoarvoja == ja != operaattorit (jotka vertailevat vain viitteitä).

Nämä ongelmat saivat kehittäjät keksimään luokkaperustaisen vaihtoehdon, joka tunnetaan nimellä Typesafe Enum. Tätä mallia on kuvattu ja kritisoitu laajalti. Joshua Bloch esitteli mallin artikkelissaan 21 Tehokas Java-ohjelmointikieliopas (Addison-Wesley, 2001) ja totesi, että sillä on joitain ongelmia; nimittäin että on hankalaa koota tyypinafe enum-vakiot joukkueiksi ja että luettelo-vakioita ei voida käyttää vaihtaa lausunnot.

Harkitse seuraavaa esimerkkiä typeafe enum -mallista. Puku luokka näyttää, kuinka voit käyttää luokkaperusteista vaihtoehtoa esitellessäsi luetellun tyypin, joka kuvaa neljä korttipukua (mailat, timantit, sydämet ja lapiot):

julkinen finaaliluokan puku // Ei pitäisi pystyä aliluokkaan Puku. {julkinen staattinen lopullinen puku CLUBS = uusi puku (); julkinen staattinen lopullinen puku DIAMONDS = uusi puku (); julkinen staattinen lopullinen puku HEARTS = uusi puku (); julkinen staattinen lopullinen Suit SPADES = uusi puku (); private Suit () {} // Ei pitäisi pystyä tuomaan lisää vakioita. }

Tämän luokan käyttämiseksi esität a Puku muuttuja ja määritä se yhdelle PukuSeuraavat vakiot:

Puku puku = Puku. DIAMONDIT;

Haluat sitten kuulustella puku jonkin sisällä vaihtaa tällainen lausunto:

kytkin (puku) {tapaus Suit.CLUBS: System.out.println ("seurat"); tauko; kotelo Suit.DIAMONDS: System.out.println ("timantit"); tauko; tapaus Suit.HEARTS: System.out.println ("sydämet"); tauko; tapaus Suit.SPADES: System.out.println ("lapiot"); }

Kuitenkin, kun Java-kääntäjä kohtaa Puku. CLUBS, se ilmoittaa virheestä toteamalla, että vaaditaan vakioilmaisu. Voit yrittää ratkaista ongelman seuraavasti:

kytkin (puku) {tapaus KLUBIT: System.out.println ("seurat"); tauko; asia DIAMONDIT: System.out.println ("timantit"); tauko; tapaus HEARTS: System.out.println ("sydämet"); tauko; tapaus SPADES: System.out.println ("lapiot"); }

Kuitenkin, kun kääntäjä kohtaa KLUBIT, se ilmoittaa virheestä, jonka mukaan se ei löytänyt symbolia. Ja vaikka sijoittaisitkin Puku paketissa, tuonut paketin ja tuonut nämä vakiot staattisesti, kääntäjä valitti, ettei se voi muuntaa Puku että int kun kohtaat puku sisään kytkin (puku). Jokaisesta tapauksessa, kääntäjä ilmoitti myös, että vaaditaan vakiolauseke.

Java ei tue Typesafe Enum -mallia vaihtaa lausunnot. Se kuitenkin esitteli typesafe enum kieliominaisuus yhdistää mallin edut ratkaistessaan sen ongelmia, ja tämä ominaisuus tukee vaihtaa.

Tyyppikohtaisen enumin julistaminen ja käyttäminen kytkinlausekkeessa

Yksinkertainen Java-koodissa typeafe enum -ilmoitus näyttää olevan samanlainen kuin C-, C ++ - ja C # -kielillä:

enum Suunta {POHJOIS, LÄNSI, ITÄ, ETELÄ}

Tässä ilmoituksessa käytetään avainsanaa enum esitellä Suunta typeafe enumina (erityinen luokka), johon voidaan lisätä mielivaltaisia ​​menetelmiä ja toteuttaa mielivaltaisia ​​rajapintoja. POHJOINEN, LÄNSI, ITÄÄNja ETELÄINENenum-vakiot toteutetaan vakiokohtaisina luokkakokonaisuuksina, jotka määrittelevät anonyymit luokat, jotka laajentavat suljinta Suunta luokassa.

Suunta ja muut tyyppihyväksynnät laskevat Enum ja periä erilaisia ​​menetelmiä, mukaan lukien arvot (), toString ()ja vertaa(), tästä luokasta. Tutkimme Enum myöhemmin tässä artikkelissa.

Listaus 1 ilmoittaa edellä mainitun enumin ja käyttää sitä a: ssa vaihtaa lausunto. Se osoittaa myös kuinka vertailla kahta enum-vakiota, jotta voidaan määrittää, mikä vakio tulee ennen toista vakiota.

Listaus 1: TEDemo.java (versio 1)

public class TEDemo {enum Direction {NORTH, WEST, EAST, SOUTH} public static void main (String [] args) {for (int i = 0; i <Suunta.arvot (). pituus; i ++) {Suunta d = Suunta .arvot () [i]; System.out.println (d); kytkin (d) {tapaus NORTH: System.out.println ("Siirry pohjoiseen"); tauko; tapaus WEST: System.out.println ("Siirry länteen"); tauko; tapaus EAST: System.out.println ("Siirry itään"); tauko; tapaus ETELÄ: System.out.println ("Siirry etelään"); tauko; oletus: väitä väärä: "tuntematon suunta"; }} System.out.println (Suunta.POHJOIS.vertailu (Suunta.NIIN)); }}

Listaus 1 ilmoittaa Suunta typeafe enum ja toistaa sen vakiojäsenet, mikä arvot () palaa. Jokaisen arvon kohdalla vaihtaa -lauseke (parannettu tukemaan tyypitafe enums) valitsee tapauksessa joka vastaa arvoad ja antaa sopivan viestin. (Et lisää etuliitteeksi enumvakiota, esim. POHJOINEN, sen enum -tyypillä.) Listaus 1 arvioi lopuksi Direction.NORTH.compareTo (Direction.SOUTH) onko se POHJOINEN tulee ennen ETELÄINEN.

Kokoa lähdekoodi seuraavasti:

javac TEDemo.java

Suorita käännetty sovellus seuraavasti:

java TEDemo

Ota huomioon seuraava tulos:

POHJOINEN Siirry pohjoiseen LÄNSI Liiku länteen EAST Siirry itään ETELÄ Siirry etelään -3

Tuotos paljastaa, että peritty toString () method palauttaa enumvakion nimen ja sen POHJOINEN tulee ennen ETELÄINEN näiden vertailuvakioiden vertailussa.

Tietojen ja käyttäytymismallien lisääminen tyyppiturvaluokkaan

Voit lisätä tietoja (kenttien muodossa) ja käyttäytymistä (menetelmien muodossa) typefe-luetteloon. Oletetaan esimerkiksi, että sinun on otettava käyttöön Kanadan kolikoiden luettelo ja että tämän luokan on tarjottava keinot palauttaa mielivaltaiseen määrään pennejä sisältyvä nikkeli, dime, neljännes tai dollari. Listaus 2 näyttää, kuinka tämä tehtävä voidaan suorittaa.

Listaus 2: TEDemo.java (versio 2)

enum Kolikko {NICKEL (5), // vakioiden on oltava ensin DIME (10), QUARTER (25), DOLLAR (100); // puolipiste vaaditaan private final int valueInPennies; Kolikko (int valueInPennies) {this.valueInPennies = valueInPennies; } int toCoins (int pennies) {return pennies / valueInPennies; }} public class TEDemo {public static void main (String [] args) {if (args.pituus! = 1) {System.err.println ("käyttö: java TEDemo määräInPennies"); palata; } int penniä = Kokonaisluku.parseInt (argumentit [0]); for (int i = 0; i <Coin.values ​​(). pituus; i ++) System.out.println (penniä + "penniä sisältää" + Coin.values ​​() [i] .toCoins (penniä) + "" + kolikko .values ​​() [i] .toString (). toLowerCase () + "s"); }}

Listaus 2 ilmoittaa ensin a Kolikko enum. Parametroitujen vakioiden luettelo tunnistaa neljä erilaista kolikkoa. Kullekin vakiolle annettu argumentti edustaa kolikon edustamien pennien määrää.

Kullekin vakiolle välitetty argumentti välitetään tosiasiallisesti Kolikko (int valueInPennies) konstruktori, joka tallentaa argumentin arvotPennies instanssikenttä. Tähän muuttujaan pääsee kolikot () instanssimenetelmä. Se jakautuu siirrettyjen pennien määrään toCoin ()S pennit parametri, ja tämä menetelmä palauttaa tuloksen, joka sattuu olemaan kolikoiden lukumäärä rahayksikössä, jonka kuvailee Kolikko vakio.

Tässä vaiheessa olet huomannut, että voit ilmoittaa ilmentymäkentät, konstruktorit ja ilmentymämenetelmät tyyppikirjastossa. Loppujen lopuksi typpeä enum on pohjimmiltaan erityinen Java-luokka.

TEDemo luokan main () method varmistaa ensin, että yksi komentoriviargumentti on määritetty. Tämä argumentti muunnetaan kokonaisluvuksi kutsumalla java.lang.I kokonaisluku luokan parseInt () method, joka jäsentää merkkijononsa argumentin arvon kokonaisluvuksi (tai heittää poikkeuksen, kun virheellinen syöte havaitaan). Minulla on enemmän sanottavaa Kokonaisluku ja serkkunsa luokkiin tulevaisuudessa Java 101 artikla.

Siirtyä eteenpäin, main () toistuu KolikkoVakiot. Koska nämä vakiot on tallennettu a Kolikko [] taulukko, main () arvioi Kolikon arvot (). Pituus tämän taulukon pituuden määrittämiseksi. Jokaiselle silmukan indeksin iteraatiolle i, main () arvioi Kolikon arvot () [i] käyttää Kolikko vakio. Se vetoaa kaikkiin kolikot () ja toString () tällä vakiolla, mikä osoittaa sen edelleen Kolikko on erityinen luokka.

Kokoa lähdekoodi seuraavasti:

javac TEDemo.java

Suorita käännetty sovellus seuraavasti:

java TEDemo 198

Ota huomioon seuraava tulos:

198 penniä sisältää 39 nikkeliä 198 penniä sisältää 19 dimeä 198 penniä sisältää 7 neljäsosaa 198 penniä sisältää 1 dollaria

Tutki Enum luokassa

Java-kääntäjä harkitsee enum syntaksisokeriksi. Kohdatessaan tyyppihyväksyntäilmoituksen se luo luokan, jonka nimi määritetään ilmoituksessa. Tämä luokka aliluokittaa abstraktin Enum luokka, joka toimii kaikkien tyyppisten kassakauppojen perusluokkana.

EnumVirallinen tyyppiparametriluettelo näyttää kamalalta, mutta sitä ei ole niin vaikea ymmärtää. Esimerkiksi Kolikko jatkaa Enumia, tulkitsisit tämän muodollisen tyypin parametriluettelon seuraavasti:

  • Mikä tahansa alaluokka Enum on annettava todellinen tyyppi argumentti Enum. Esimerkiksi, KolikkoOtsikko määrittelee Enum.
  • Todellisen tyypin argumentin on oltava alaluokka Enum. Esimerkiksi, Kolikko on ryhmän alaluokka Enum.
  • Alaluokka Enum (kuten Kolikko) on noudatettava sanamuotoa, jonka mukaan se toimittaa oman nimensä (Kolikko) todellisena tyypin argumenttina.

Tutki EnumJava-dokumentaatio ja huomaat, että se ohittaa java.lang.objektion klooni(), on yhtä suuri (), viimeistellä (), hash koodin()ja toString () menetelmiä. Paitsi toString (), kaikki nämä ensisijaiset menetelmät ilmoitetaan lopullinen jotta niitä ei voida ohittaa alaluokassa:

  • klooni() ohitetaan, jotta estetään vakioiden kloonaus niin, että vakiosta ei ole koskaan enemmän kuin yksi kopio; muuten vakioita ei voitu verrata kautta == ja !=.
  • on yhtä suuri () ohitetaan vakioiden vertaamiseksi viitteiden avulla. Vakiot, joilla on sama identiteetti (==) on oltava sama sisältö (on yhtä suuri ()), ja erilaiset identiteetit tarkoittavat erilaista sisältöä.
  • viimeistellä () ohitetaan sen varmistamiseksi, että vakioita ei voida viimeistellä.
  • hash koodin() ohitetaan, koska on yhtä suuri () ohitetaan.
  • toString () ohitetaan vakion nimen palauttamiseksi.

Enum tarjoaa myös omat menetelmänsä. Näihin menetelmiin kuuluvat lopullinenvertaa() (Enum toteuttaa java.lang.Vertailtavissa käyttöliittymä), getDeclaringClass (), nimi()ja järjestysnumero () menetelmät: