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.
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 Puku
Seuraavat 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ÄÄN
ja ETELÄINEN
enum-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 Kolikko
Vakiot. 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.
Enum
Virallinen 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 argumenttiEnum
. Esimerkiksi,Kolikko
Otsikko määritteleeEnum
. - Todellisen tyypin argumentin on oltava alaluokka
Enum
. Esimerkiksi,Kolikko
on ryhmän alaluokkaEnum
. - Alaluokka
Enum
(kutenKolikko
) on noudatettava sanamuotoa, jonka mukaan se toimittaa oman nimensä (Kolikko
) todellisena tyypin argumenttina.
Tutki Enum
Java-dokumentaatio ja huomaat, että se ohittaa java.lang.objekti
on 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, koskaon yhtä suuri ()
ohitetaan.toString ()
ohitetaan vakion nimen palauttamiseksi.
Enum
tarjoaa myös omat menetelmänsä. Näihin menetelmiin kuuluvat lopullinen
vertaa()
(Enum
toteuttaa java.lang.Vertailtavissa
käyttöliittymä), getDeclaringClass ()
, nimi()
ja järjestysnumero ()
menetelmät: