Ohjelmointi

Kuinka käyttää Java-geneerisiä aineita välttämään ClassCastExceptions

Java 5 toi geneeriset tiedot Java-kielelle. Tässä artikkelissa esitän sinulle geneerisiä aineita ja keskustelen geneerisistä tyyppeistä, geneerisistä menetelmistä, geneerisistä aineista ja tyyppipäätöksistä, geneerisistä kiistoista sekä geneerisistä aineista ja kasan pilaantumisesta.

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

Mitä ovat geneeriset lääkkeet?

Yleiset ovat joukko toisiinsa liittyviä kieliominaisuuksia, joiden avulla tyypit tai menetelmät voivat toimia erityyppisissä kohteissa ja samalla tarjota käännösajan tyyppisen turvallisuuden. Yleisillä ominaisuuksilla käsitellään java.lang.ClassCastExceptionNe heitetään ajon aikana, jotka ovat seurausta koodista, joka ei ole tyyppiturvallinen (eli esineiden heittäminen nykyisistä tyypeistään yhteensopimattomiin tyyppeihin).

Generics ja Java Collections Framework

Geneerisiä aineita käytetään laajalti Java Collections Frameworkissa (otetaan virallisesti käyttöön tulevaisuudessa Java 101 artikkelit), mutta ne eivät ole yksinomaan sitä. Geneerisiä aineita käytetään myös muualla Java-vakioluokan kirjastossa java.lang.luokka, java.lang.Vertailukelpoinen, java.lang.ThreadLocalja java.lang.ref.WeakReference.

Harkitse seuraavaa koodinpätkää, joka osoittaa tyyppiturvallisuuden puutteen (Java Collections Frameworkin yhteydessä) java.util.LinkedList luokka), joka oli yleistä Java-koodissa ennen geneeristen lääkkeiden käyttöönottoa:

Lista doubleList = uusi LinkedList (); doubleList.add (uusi Double (3.5)); Tupla d = (Tupla) doubleList.iterator (). Seuraava ();

Vaikka yllä olevan ohjelman tavoitteena on tallentaa vain java.lang.Tupla luettelossa olevia objekteja, mikään ei estä muunlaisten objektien tallentamista. Voit esimerkiksi määrittää doubleList.add ("Hei"); lisätä a java.lang.String esine. Kun kuitenkin varastoidaan toisenlaista esinettä, viimeinen rivi (Kaksinkertainen) valukäyttäjä aiheuttaa ClassCastException heitettävä joutuessaan kohtaamaanKaksinkertainen esine.

Koska tämä tyyppiturvallisuuden puute havaitaan vasta ajon aikana, kehittäjä ei ehkä ole tietoinen ongelmasta ja jättää sen asiakkaan (kääntäjän sijasta) löydettäväksi. Yleisohjeet auttavat kääntäjää varoittamaan kehittäjää ongelmasta, joka liittyy objektin tallentamiseen muuhun kuinKaksinkertainen kirjoita luetteloon antamalla kehittäjän merkitä luettelo vain sisältäväksi Kaksinkertainen esineitä. Tämä apu on osoitettu alla:

Lista doubleList = uusi LinkedList (); doubleList.add (uusi Double (3.5)); Tupla d = doubleList.iterator (). Seuraava ();

Lista lukee nytLista / Kaksinkertainen.” Lista on yleinen käyttöliittymä ilmaistuna muodossa Lista, joka vie a Kaksinkertainen type-argumentti, joka määritetään myös varsinaisen objektin luomisessa. Kääntäjä voi nyt pakottaa tyypin oikeellisuuden lisäämällä objektia luetteloon - esimerkiksi luettelo voi tallentaa Kaksinkertainen vain arvot. Tämä täytäntöönpano poistaa tarpeen (Kaksinkertainen) heittää.

Yleisten tyyppien löytäminen

A yleinen tyyppi on luokka tai käyttöliittymä, joka esittelee parametrisoitujen tyyppien joukon a: n kautta muodollinen tyypin parametriluettelo, joka on pilkulla erotettu luettelo tyypin parametrien nimistä kulmasulkeiden parin välillä. Yleiset tyypit noudattavat seuraavaa syntaksia:

luokassa tunniste<formalTypeParameterList> {// class body} -käyttöliittymä tunniste<formalTypeParameterList> {// käyttöliittymän runko}

Java Collections Framework tarjoaa monia esimerkkejä yleistyypeistä ja niiden parametriluetteloista (ja viittaan niihin tässä artikkelissa). Esimerkiksi, java.util.Set on yleinen tyyppi, on sen muodollinen tyyppiparametriluettelo ja E on luettelon yksinäinen tyypin parametri. Toinen esimerkki onjava.util.Kartta.

Java-tyypin parametrien nimeämiskäytäntö

Java-ohjelmointikäytäntö määrää, että tyypin parametrien nimet ovat yksittäisiä isoja kirjaimia, kuten E elementille, K avaimelle, V arvolle ja T tyypille. Vältä mahdollisuuksien mukaan merkityksettömän nimen, kuten Pjava.util.List tarkoittaa luetteloa elementeistä, mutta mitä voisit tarkoittaa tällä Lista

A parametrisoitu tyyppi on yleinen tyypin esiintymä, jossa yleisen tyypin tyypin parametrit korvataan todelliset tyypin argumentit (tyyppinimet). Esimerkiksi, Aseta on parametrisoitu tyyppi missä Merkkijono on todellinen tyyppi-argumentti, joka korvaa tyypin parametrin E.

Java-kieli tukee seuraavanlaisia ​​todellisia tyyppiargumentteja:

  • Betonityyppi: Luokan tai muun viitetyypin nimi välitetään tyypin parametrille. Esimerkiksi Lista, Eläin siirretään E.
  • Betonin parametrisoitu tyyppi: Parametroitu tyypin nimi välitetään tyypin parametrille. Esimerkiksi Aseta, Lista siirretään E.
  • Taulukon tyyppi: Taulukko välitetään tyypin parametrille. Esimerkiksi Kartta, Merkkijono siirretään K ja Merkkijono [] siirretään V.
  • Tyyppi-parametri: Tyyppiparametri välitetään tyypin parametrille. Esimerkiksi luokka Container {Set elements; }, E siirretään E.
  • Jokerimerkki: Kysymysmerkki (?) välitetään tyypin parametrille. Esimerkiksi Luokka, ? siirretään T.

Jokainen yleinen tyyppi tarkoittaa a: n olemassaoloa raaka tyyppi, joka on yleinen tyyppi ilman muodollista tyyppiparametriluetteloa. Esimerkiksi, Luokka on raaka tyyppi Luokka. Toisin kuin yleiset tyypit, raakatyyppejä voidaan käyttää minkä tahansa kohteen kanssa.

Yleisten tyyppien ilmoittaminen ja käyttö Java-käyttöjärjestelmässä

Yleisen tyypin ilmoittaminen edellyttää muodollisen tyyppiparametriluettelon määrittämistä ja pääsyä näihin tyyppiparametreihin koko toteutuksen ajan. Yleisen tyypin käyttäminen edellyttää, että todelliset tyyppiargumentit välitetään sen tyyppiparametreille, kun yleistyyppiä selvitetään. Katso luettelo 1.

Listaus 1:GenDemo.java (versio 1)

luokka Container {private E [] -elementit; yksityinen int-indeksi; Container (int size) {elements = (E []) new Object [size]; indeksi = 0; } void add (E-elementti) {elements [index ++] = elementti; } E get (int index) {return elementit [index]; } int koko () {paluuindeksi; }} public class GenDemo {public static void main (String [] argumentit) {Container con = uusi Container (5); jatko ("pohjoinen"); jatko ("etelä"); con.add ("itä"); jatko ("länsi"); for (int i = 0; i <koko. (); i ++) System.out.println (con.get (i)); }}

Listaus 1 osoittaa yleisen tyypin ilmoituksen ja käytön yksinkertaisen säilötyypin yhteydessä, joka tallentaa sopivan argumenttityypin objektit. Koodin yksinkertaisuuden vuoksi olen jättänyt virheen tarkistuksen pois.

Kontti luokka ilmoittaa olevansa yleinen tyyppi määrittelemällä muodollinen tyypin parametriluettelo. Tyyppi-parametri E käytetään tunnistamaan tallennettujen elementtien tyyppi, sisäiseen ryhmään lisättävä elementti ja palautustyyppi elementtiä noudettaessa.

Kontti (sis. Koko) konstruktori luo matriisin kautta elementit = (E []) uusi objekti [koko];. Jos mietit, miksi en määrittänyt elementit = uusi E [koko];, syy on se, että se ei ole mahdollista. Se voi johtaa a ClassCastException.

Koosta luettelo 1 (javac GenDemo.java). (E []) cast saa kääntäjän antamaan varoituksen valinnan estämisestä. Se merkitsee mahdollisuutta, että downcasting from Esine[] että E [] saattaa rikkoa tyyppiturvallisuutta, koska Esine[] voi tallentaa minkä tahansa tyyppisen objektin.

Huomaa kuitenkin, että tässä esimerkissä ei voida mitenkään rikkoa tyyppiturvallisuutta. Ei yksinkertaisesti ole mahdollista tallentaaE objekti sisäisessä taulukossa. Etuliite Kontti (sis. Koko) rakentaja kanssa @SuppressWarnings ("tarkistamaton") tukahduttaisi tämän varoitusviestin.

Suorittaa java GenDemo tämän sovelluksen suorittamiseksi. Ota huomioon seuraava tulos:

pohjoinen Etelä Itä Länsi

Rajoittavat tyypin parametrit Java-ohjelmassa

E sisään Aseta on esimerkki rajoittamaton tyypin parametri koska voit välittää minkä tahansa todellisen tyypin argumentin E. Voit esimerkiksi määrittää Aseta, Asetatai Aseta.

Joskus haluat rajoittaa tyypin todellisten argumenttien tyyppejä, jotka voidaan välittää tyypin parametrille. Esimerkiksi, haluat ehkä rajoittaa tyypin parametrin hyväksymään vain Työntekijä ja sen alaluokat.

Voit rajoittaa tyypin parametria määrittämällä yläraja, joka on tyyppi, joka toimii ylärajana tyyppeille, jotka voidaan välittää todellisina tyyppiargumentteina. Määritä yläraja varatulla sanalla ulottuu jota seuraa ylärajan tyypin nimi.

Esimerkiksi, luokan työntekijät rajoittaa tyyppejä, joille voidaan välittää Työntekijät että Työntekijä tai alaluokka (esim. Kirjanpitäjä). Määritetään uudet työntekijät olisi laillista, kun taas uudet työntekijät olisi laitonta.

Voit määrittää tyypin parametrille useamman kuin yhden ylärajan. Ensimmäisen sidoksen on kuitenkin aina oltava luokka, ja lisärajoitusten tulee aina olla rajapintoja. Jokainen sidos erotetaan edeltäjäänsä tähdellä (&). Tutustu luetteloon 2.

Listaus 2: GenDemo.java (versio 2)

tuo java.math.BigDecimal; tuo java.util.Arrays; abstrakti luokka Työntekijä {yksityinen BigDecimal hourlySalary; yksityinen merkkijono nimi; Työntekijä (jonon nimi, BigDecimal hourlySalary) {tämä.nimi = nimi; this.hourlySalary = hourlySalary; } public BigDecimal getHourlySalary () {return hourlySalary; } public String getName () {palautusnimi; } public String toString () {return name + ":" + hourlySalary.toString (); }} -luokan kirjanpitäjä laajentaa työntekijöiden toteutuksia Vertailukelpoinen {kirjanpitäjä (merkkijonon nimi, BigDecimal hourlySalary) {super (nimi, hourlySalary); } public int CompareTo (Accountant acct) {return getHourlySalary (). CompareTo (acct.getHourlySalary ()); }} luokka SortedEmployees {yksityiset E [] työntekijät; yksityinen int-indeksi; @SuppressWarnings ("tarkistamaton") LajiteltuTyöntekijät (int-koko) {työntekijät = (E []) uusi työntekijä [koko]; int-indeksi = 0; } void add (E emp) {työntekijät [hakemisto ++] = emp; Taulukot.lajittelu (työntekijät, 0, hakemisto); } E get (int index) {palaa työntekijöitä [index]; } int koko () {paluuindeksi; }} public class GenDemo {public static void main (String [] args) {SortedEmployees se = new SortedEmployees (10); se.add (uusi kirjanpitäjä ("John Doe", uusi BigDecimal ("35.40"))); se.add (uusi kirjanpitäjä ("George Smith", uusi BigDecimal ("15,20"))); se.add (uusi kirjanpitäjä ("Jane Jones", uusi BigDecimal ("25,60"))); for (int i = 0; i <se.size (); i ++) System.out.println (se.get (i)); }}

Listaus 2 Työntekijä luokka tiivistää tuntipalkkaa saavan työntekijän käsitteen. Tämä luokka on alaluokassa Kirjanpitäjä, joka myös toteuttaa Vertailukelpoinen osoittamaan sen KirjanpitäjäNiitä voidaan verrata niiden luonnollisen järjestyksen mukaan, joka sattuu olemaan tuntipalkka tässä esimerkissä.

java.lang.Vertailtavissa käyttöliittymä ilmoitetaan yleiseksi tyypiksi, jolla on yhden tyyppinen parametri T. Tämä käyttöliittymä tarjoaa int vertaa (T o) menetelmä, joka vertaa nykyistä objektia argumenttiin (tyypin T), palauttamalla negatiivisen kokonaisluvun, nollan tai positiivisen kokonaisluvun, koska tämä objekti on pienempi, yhtä suuri tai suurempi kuin määritetty objekti.

LajiteltuTyöntekijät luokan avulla voit tallentaa Työntekijä alaluokan esiintymät, jotka toteutetaan Vertailukelpoinen sisäisessä taulukossa. Tämä taulukko on lajiteltu ( java.util.Arrays luokan void sort (Object [] a, int fromIndex, int toIndex) luokan menetelmä) tuntipalkan nousevassa järjestyksessä Työntekijä alaluokan esiintymä lisätään.

Koosta luettelo 2 (javac GenDemo.java) ja suorita sovellus (java GenDemo). Ota huomioon seuraava tulos:

George Smith: 15.20 Jane Jones: 25.60 John Doe: 35.40

Alemmat rajat ja yleiset tyypin parametrit

Et voi määrittää yleistä tyypin parametrin alarajaa. Ymmärtääkseni, miksi suosittelen lukemaan Angelika Langerin Java Genericsin usein kysytyt kysymykset alarajoista, jotka hänen mukaansa "olisivat hämmentäviä eikä erityisen hyödyllisiä".

Ottaen huomioon jokerimerkit

Oletetaan, että haluat tulostaa luettelon objekteista riippumatta siitä, ovatko nämä merkkijonot, työntekijät, muodot vai jonkin muun tyyppisiä. Ensimmäinen yrityksesi saattaa näyttää siltä, ​​mikä näkyy luettelossa 3.

Listaus 3: GenDemo.java (versio 3)

tuo java.util.ArrayList; tuoda java.util.Iterator; tuo java.util.List; julkinen luokka GenDemo {public static void main (String [] args) {Lista reittiohjeet = new ArrayList (); reittiohjeet.add ("pohjoinen"); reittiohjeet.add ("etelä"); reittiohjeet.add ("itä"); reittiohjeet.add ("länsi"); printList (ohjeet); Luetteloluokat = new ArrayList (); arvosanat. lisää (uusi kokonaisluku (98)); arvosanat lisää (uusi kokonaisluku (63)); arvosanat. lisää (uusi kokonaisluku (87)); printList (arvosanat); } staattinen void printList (luetteloluettelo) {Iterator iter = list.iterator (); while (iter.hasNext ()) System.out.println (iter.next ()); }}

Vaikuttaa loogiselta, että merkkijonoluettelo tai kokonaislukujen luettelo on objektiluettelon alatyyppi, mutta kääntäjä valittaa, kun yrität koota tätä luetteloa. Tarkemmin sanottuna se kertoo, että merkkijonoluetteloa ei voida muuntaa objektiluetteloksi, ja vastaavasti kokonaislukulistalle.

Saamasi virheilmoitus liittyy geneeristen lääkkeiden perussääntöön:

Copyright fi.verticalshadows.com 2021