Programavimas

Kaip naudoti „Java“ generiką, kad išvengtumėte „ClassCastExceptions“

„Java 5“ atvedė generinius vaistus į „Java“ kalbą. Šiame straipsnyje aš supažindinu jus su generiniais vaistais ir aptariu generinius tipus, bendrinius metodus, generinius ir tipų išvada, generikų ginčus, generinius vaistus ir krūvos taršą.

atsisiųsti Gauti kodą Atsisiųskite šios „Java 101“ mokymo programos pavyzdžių šaltinio kodą. Sukūrė Jeffas Friesenas, skirtas „JavaWorld“.

Kas yra generiniai vaistai?

Generikai yra susijusių kalbų ypatybių rinkinys, leidžiantis tipams ar metodams valdyti įvairių tipų objektus, tuo pačiu užtikrinant kompiliavimo laiko saugumą. Bendrosios savybės sprendžia java.lang.ClassCastExceptions metami vykdymo metu, o tai yra kodo, kuris nėra saugus tipas, rezultatas (t. y. objektų liejimas iš dabartinių tipų į nesuderinamus).

„Generics“ ir „Java Collections Framework“

Generics yra plačiai naudojami „Java Collections Framework“ (oficialiai pristatomi ateityje „Java 101“ straipsniai), tačiau jie nėra jai išskirtiniai. Generikai taip pat naudojami kitose „Java“ standartinių klasių bibliotekos dalyse, įskaitant java.lang.Klasė, java.lang.Palyginamas, java.lang.ThreadLocalir java.lang.ref.WeakReference.

Apsvarstykite šį kodo fragmentą, kuris rodo tipo saugumo trūkumą („Java Collections Framework“ kontekste java.util.LinkedList klasė), kuris buvo įprastas „Java“ kode prieš įvedant generinius:

Sąrašas doubleList = new LinkedList (); doubleList.add (naujas Double (3.5)); Dvigubas d = (Double) doubleList.iterator (). Kitas ();

Nors pirmiau minėtos programos tikslas yra tik saugoti java.lang.Dvigubai sąrašą, niekas netrukdo saugoti kitų rūšių objektus. Pavyzdžiui, galite nurodyti doubleList.add („Labas“); pridėti a java.lang.Stringas objektas. Tačiau saugant kitos rūšies objektą, paskutinę eilutę (Dvivietis) dauguma operatoriaus sukelia „ClassCastException“ išmesti susidūrus su neDvigubai objektas.

Kadangi šis tipo saugos trūkumas nenustatomas iki vykdymo laiko, kūrėjas gali nežinoti problemos, palikdamas ją klientui (o ne kompiliatoriui) atrasti. „Generics“ padeda kompiliatoriui įspėti kūrėją apie objekto saugojimo su neDvigubai įveskite į sąrašą, leisdami kūrėjui pažymėti, kad sąraše yra tik Dvigubai objektai. Ši pagalba parodyta žemiau:

Sąrašas doubleList = new LinkedList (); doubleList.add (naujas Double (3.5)); Dvigubas d = doubleList.iterator (). Kitas ();

Sąrašas dabar skaito „Sąrašas apie Dvigubai.” Sąrašas yra bendroji sąsaja, išreikšta kaip Sąrašas, tam reikia a Dvigubai tipo argumentas, kuris taip pat nurodomas kuriant faktinį objektą. Kompiliatorius dabar gali įtvirtinti tipo teisingumą, kai įtraukia objektą į sąrašą, pavyzdžiui, sąrašą galima išsaugoti Dvigubai tik vertybės. Šis vykdymas pašalina poreikį (Dvivietis) mesti.

Atrasti bendrinius tipus

A bendrinis tipas yra klasė arba sąsaja, įvedanti parametrų tipų rinkinį per formalaus tipo parametrų sąrašas, kuris yra kableliais atskirtas tipų parametrų pavadinimų sąrašas tarp kampinių skliaustų poros. Bendrieji tipai atitinka šią sintaksę:

klasė identifikatorius<formalTypeParameterList> {// class body} sąsaja identifikatorius<formalTypeParameterList> {// sąsajos turinys}

„Java Collections Framework“ pateikia daug bendrų tipų ir jų parametrų sąrašų pavyzdžių (ir aš juos remiuosi šiame straipsnyje). Pavyzdžiui, java.util.Rinkinys yra bendrinis tipas, yra jo oficialus tipo parametrų sąrašas ir E yra sąrašo pavienio tipo parametras. Kitas pavyzdys yrajava.util.Žemėlapis.

„Java“ tipo parametrų pavadinimo tvarka

„Java“ programavimo sutartis nurodo, kad tipo parametrų pavadinimai turi būti vienos didžiosios raidės, pvz E elementui, K. raktui, V už vertę ir T tipui. Jei įmanoma, venkite naudoti beprasmį pavadinimą, pvz Pjava.util.Sąrašas reiškia elementų sąrašą, bet ką galėtumėte pasakyti Sąrašas

A parametruojamas tipas yra bendro tipo egzempliorius, kuriame pakeičiami bendro tipo tipo parametrai faktinio tipo argumentai (tipų pavadinimai). Pavyzdžiui, Nustatyti yra parametruojamas tipas, kur Stygos yra tikrasis tipo argumentas, pakeičiantis tipo parametrą E.

„Java“ kalba palaiko šiuos faktinio tipo argumentus:

  • Betono tipas: Klasės ar kito nuorodos tipo pavadinimas perduodamas tipo parametrui. Pavyzdžiui, Sąrašas, Gyvūnas yra perduodama E.
  • Betono parametruojamas tipas: Parametruotas tipo pavadinimas perduodamas tipo parametrui. Pavyzdžiui, Nustatyti, Sąrašas yra perduodama E.
  • Masyvo tipas: Masyvas perduodamas tipo parametrui. Pavyzdžiui, Žemėlapis, Stygos yra perduodama K. ir Stygos [] yra perduodama V.
  • Tipo parametras: Tipo parametras perduodamas tipo parametrui. Pavyzdžiui, klasės konteineris {Nustatyti elementus; }, E yra perduodama E.
  • Pakaitos simbolis: Klausiamasis ženklas (?) perduodamas tipo parametrui. Pavyzdžiui, Klasė, ? yra perduodama T.

Kiekvienas bendrinis tipas reiškia a egzistavimą žalio tipo, kuris yra bendrasis tipas be oficialaus tipo parametrų sąrašo. Pavyzdžiui, Klasė yra neapdorotas Klasė. Skirtingai nuo bendrų tipų, neapdorotus tipus galima naudoti su bet kokio tipo objektais.

Bendrų tipų deklaravimas ir naudojimas „Java“

Deklaruojant bendrąjį tipą, reikia nurodyti oficialų tipo parametrų sąrašą ir pasiekti šiuos tipo parametrus jo įgyvendinimo metu. Naudojant bendrąjį tipą, tikrojo tipo argumentai perduodami jo tipo parametrams, kai nustatomas bendrasis tipas. Žr. 1 sąrašą.

1 sąrašas:GenDemo.java (1 versija)

klasės „Container {private E []“ elementai; privatus int indeksas; Konteineris (int dydis) {elementai = (E []) naujas objektas [dydis]; indeksas = 0; } void add (E elementas) {elementai [rodyklė ++] = elementas; } E get (int index) {return elementai [rodyklė]; } int dydis () {grąžos indeksas; }} public class GenDemo {public static void main (String [] args) {Container con = new Container (5); con.add („Šiaurė“); con.add („Pietūs“); con.add („Rytai“); con.add („Vakarai“); for (int i = 0; i <con.size (); i ++) System.out.println (con.get (i)); }}

1 sąrašas parodo bendro tipo deklaravimą ir naudojimą paprasto konteinerio tipo, kuriame saugomi atitinkamo argumento tipo objektai, kontekste. Kad kodas būtų paprastas, praleidau klaidų tikrinimą.

Konteineris klasė pasiskelbia esanti bendro tipo, nurodydama formalaus tipo parametrų sąrašas. Tipo parametras E naudojamas identifikuoti saugomų elementų tipą, elementą, kurį reikia pridėti prie vidinio masyvo, ir grąžinimo tipą, kai gaunamas elementas.

Konteineris (int dydis) konstruktorius sukuria masyvą per elementai = (E []) naujas objektas [dydis];. Jei įdomu, kodėl nenurodžiau elementai = naujas E [dydis];, priežastis ta, kad tai neįmanoma. Tai padarius, gali atsirasti a „ClassCastException“.

Sudaryti 1 sąrašą (javac GenDemo.java). (E []) „cast“ verčia kompiliatorių pateikti įspėjimą apie tai, kad aktoriai nėra pažymėti. Tai pažymi galimybę, kad žeminimas iš Objektas [] į E [] gali pažeisti tipo saugumą, nes Objektas [] gali saugoti bet kokio tipo objektus.

Tačiau atkreipkite dėmesį, kad šiame pavyzdyje jokiu būdu negalima pažeisti tipo saugos. Tiesiog neįmanoma laikyti neE objektas vidiniame masyve. Priešdėlis Konteineris (int dydis) konstruktorius su @SuppressWarnings ("nepažymėta") nuslopintų šį įspėjamąjį pranešimą.

Vykdyti java GenDemo paleisti šią programą. Turėtumėte stebėti šį rezultatą:

Šiaurė, pietūs, rytai, vakarai

Apribojimo tipo parametrai „Java“

E į Nustatyti yra pavyzdys neriboto tipo parametras nes galite perduoti bet kokį faktinio tipo argumentą E. Pavyzdžiui, galite nurodyti Nustatyti, Nustatytiarba Nustatyti.

Kartais norėsite apriboti faktinio tipo argumentų tipus, kuriuos galima perduoti tipo parametrui. Pvz., Galbūt norite apriboti tipo parametrą, kad jis būtų priimtas tik Darbuotojas ir jo poklasiai.

Tipo parametrą galite apriboti nurodydami viršutinė riba, kuris yra tipas, kuris naudojamas kaip viršutinė riba tipams, kuriuos galima perduoti kaip faktinius tipo argumentus. Nurodykite viršutinę ribą naudodami rezervuotą žodį tęsiasi po kurio nurodomas viršutinės ribos tipo pavadinimas.

Pavyzdžiui, klasės darbuotojai apriboja tipus, kuriems galima perduoti Darbuotojai į Darbuotojas arba poklasis (pvz., Buhalterė). Nurodant nauji darbuotojai būtų teisėta, o nauji darbuotojai būtų neteisėta.

Tipo parametrui galite priskirti daugiau nei vieną viršutinę ribą. Tačiau pirmoji riba visada turi būti klasė, o papildomos ribos visada turi būti sąsajos. Kiekvieną įrišimą nuo pirmtako skiria ampersandas (&). Peržiūrėkite 2 sąrašą.

2 sąrašas: GenDemo.java (2 versija)

importuoti java.math.BigDecimal; importuoti java.util.Arrays; abstrakti klasė Darbuotojas {private BigDecimal hourlySalary; asmeninės eilutės pavadinimas; Darbuotojas (eilutės pavadinimas, „BigDecimal hourlySalary“) {this.name = vardas; this.hourlySalary = valandinis atlygis; } public BigDecimal getHourlySalary () {return hourlySalary; } public String getName () {grąžinimo vardas; } public String toString () {return name + ":" + hourlySalary.toString (); }} klasė Buhalteris praplečia Darbuotojų įrankius Palyginamieji {buhalteris (eilutės pavadinimas, BigDecimal hourlySalary) {super (vardas, hourlySalary); } public int CompareTo (Accountant acct) {return getHourlySalary (). CompareTo (acct.getHourlySalary ()); }} klasės Rūšiuoti darbuotojai {privatūs E [] darbuotojai; privatus int indeksas; @SuppressWarnings ("nepažymėtas") RūšiuotiDarbuotojai (int dydis) {darbuotojai = (E []) naujas darbuotojas [dydis]; int indeksas = 0; } void add (E emp) {darbuotojai [rodyklė ++] = emp; Masyvai.rūšiuoti (darbuotojai, 0, indeksas); } E get (int index) {grįžtantys darbuotojai [indeksas]; } int dydis () {grąžos indeksas; }} public class GenDemo {public static void main (String [] args) {SortedEmployees se = new SortedEmployees (10); se.add (naujas buhalteris („John Doe“, naujas „BigDecimal“ („35.40“))); se.add (naujas buhalteris („George Smith“, naujas „BigDecimal“ („15.20“))); se.add (nauja buhalterė („Jane Jones“, nauja „BigDecimal“ („25,60“))); for (int i = 0; i <se.size (); i ++) System.out.println (se.get (i)); }}

2 sąrašas Darbuotojas klasė apibendrina darbuotojo, gaunančio valandinį atlygį, sampratą. Ši klasė yra subklasė Buhalterė, kuris taip pat įgyvendina Palyginamas kad tai nurodytų BuhalterėS galima palyginti pagal jų natūralią tvarką, kuri šiame pavyzdyje būna valandinis atlygis.

java.lang.Palyginamas sąsaja yra deklaruojama kaip bendrasis tipas su vieno tipo parametru, pavadintu T. Ši sąsaja suteikia int palygintiTo (T o) metodas, lyginantis esamą objektą su argumentu (tipo T), grąžinant neigiamą sveiką skaičių, nulį arba teigiamą sveiką skaičių, nes šis objektas yra mažesnis, lygus arba didesnis už nurodytą objektą.

RūšiuotiDarbuotojai klasė leidžia jums laikyti Darbuotojas poklasio egzemplioriai, kurie įgyvendinami Palyginamas vidiniame masyve. Šis masyvas yra rūšiuojamas (per java.util.A arrays klasės void sort (Object [] a, int fromIndex, int toIndex) klasės metodas) didėjančia valandinio atlygio tvarka po Darbuotojas pridedama poklasio egzempliorius.

Sudaryti 2 sąrašą (javac GenDemo.java) ir paleiskite programą (java GenDemo). Turėtumėte stebėti šį rezultatą:

George'as Smithas: 15.20 Jane Jones: 25.60 John Doe: 35.40

Apatinės ribos ir bendro tipo parametrai

Negalite nurodyti apatinės ribos bendro tipo parametrui. Norėdami suprasti, kodėl aš rekomenduoju perskaityti Angelikos Langer „Java Generics“ DUK apie apatines ribas, kurie, jos teigimu, „būtų painūs ir ne itin naudingi“.

Atsižvelgiant į pakaitos simbolius

Tarkime, kad norite atsispausdinti objektų sąrašą, neatsižvelgiant į tai, ar šie objektai yra eilutės, darbuotojai, formos ar kokio kito tipo. Pirmasis jūsų bandymas gali atrodyti taip, kaip parodyta 3 sąraše.

3 sąrašas: GenDemo.java (3 versija)

importuoti java.util.ArrayList; importuoti java.util.Iterator; importuoti java.util.List; public class GenDemo {public static void main (String [] args) {Nurodyti nuorodas = new ArrayList (); kryptys.add ("šiaurė"); kryptys.add ("pietūs"); kryptys.add ("rytai"); kryptys.add ("vakarai"); printList (kryptys); Sąrašų sąrašas = naujas „ArrayList“ (); pažymiai.add (naujas sveikasis skaičius (98)); pažymiai.add (naujas sveikasis skaičius (63)); pažymiai.add (naujas sveikasis skaičius (87)); printList (pažymiai); } static void printList (Sąrašų sąrašas) {Iterator iter = list.iterator (); while (iter.hasNext ()) System.out.println (iter.next ()); }}

Atrodo logiška, kad eilučių arba sveikųjų skaičių sąrašas yra objektų sąrašo potipis, tačiau kompiliatorius skundžiasi, kai bandote sudaryti šį sąrašą. Tiksliau sakant, jums sakoma, kad eilutės sąrašas negali būti paverstas objekto sąrašu ir panašiai kaip sveikų skaičių sąrašas.

Gautas klaidos pranešimas yra susijęs su pagrindine generikų taisykle:

Copyright lt.verticalshadows.com 2023