Tyyppien yhteensopivuuden ymmärtäminen on perustavaa laatua hyville Java-ohjelmille, mutta Java-kielielementtien välisten variaatioiden vuorovaikutus voi tuntua aloittelijalle erittäin akateemiselta. Tämä kaksiosainen artikkeli on tarkoitettu ohjelmistokehittäjille, jotka ovat valmiita vastaamaan haasteeseen! Osa 1 paljasti kovariaattiset ja ristiriitaiset suhteet yksinkertaisempien elementtien, kuten taulukotyyppien ja yleisten tyyppien, sekä erityisen Java-kielielementin, jokerin, välillä. Osa 2 tutkii tyyppiriippuvuutta Java Collections -sovellusliittymässä, yleisissä ja lambda-lausekkeissa.
Hyppäämme suoraan sisään, joten jos et ole vielä lukenut osaa 1, suosittelen aloittamista siitä.
API-esimerkkejä ristiriitaisuudesta
Harkitse ensimmäisessä esimerkissämme Vertailija
version java.util.Collections.sort ()
, Java Collections -sovellusliittymästä. Tämän menetelmän allekirjoitus on:
void sort (Luetteloluettelo, vertailija c)
järjestellä()
menetelmä lajittelee kaikki Lista
. Yleensä ylikuormitetun version käyttö on helpompaa allekirjoituksella:
lajittelu (Lista)
Tässä tapauksessa, ulottuu Vertailukelpoinen
ilmaisee, että järjestellä()
voidaan kutsua vain, jos tarvittavat menetelmien vertailuelementit (nimittäin vertaa)
on määritelty elementtityypissä (tai sen supertyypissä ? super T)
:
sort (integerList); // Kokonaisluku toteuttaa Vertailukelpoinen lajittelu (asiakaslista); // toimii vain, jos asiakas toteuttaa vertailukelpoisen
Geneeristen lääkkeiden käyttö vertailuun
Luonnollisesti luettelo on lajiteltavissa vain, jos sen osia voidaan verrata keskenään. Vertailu tehdään yhdellä menetelmällä vertaa
, joka kuuluu käyttöliittymään Vertailukelpoinen
. Sinun on toteutettava vertaa
elementtiluokassa.
Tämän tyyppinen elementti voidaan kuitenkin lajitella vain yhdellä tavalla. Voit esimerkiksi lajitella a Asiakas
henkilötodistuksensa, mutta ei syntymäpäivän tai postinumeron perusteella. Käyttämällä Vertailija
version järjestellä()
on joustavampi:
publicstatic void sort (luetteloluettelo, vertailija c)
Nyt verrataan elementtejä ei elementtiluokkaan, vaan lisäosaan Vertailija
esine. Tällä yleisellä rajapinnalla on yksi objektimenetelmä:
int vertaa (T o1, T02);
Suhteelliset parametrit
Objektin pikakäsittely useammin kuin kerran antaa sinun lajitella objekteja eri ehtojen mukaan. Mutta tarvitsemmeko todella niin monimutkaista Vertailija
tyypin parametri? Useimmissa tapauksissa, Vertailija
riittäisi. Voisimme käyttää sitä vertailla()
menetelmä verrata mitä tahansa kahta elementtiä Lista
esine seuraavasti:
class DateComparator toteuttaa Comparator {public int vertailla (Date d1, Date d2) {return ...} // vertaa kahta Date-objektia} List dateList = ...; // Päivämääräobjektien luettelo (dateList, new DateComparator ()); // lajittelee dateList
Menetelmän monimutkaisemman version käyttäminen Collection.sort ()
kuitenkin asettanut meidät uusiin käyttötapauksiin. Kiistanalainen tyypin parametri Vertailukelpoinen
mahdollistaa lajittelulistan lajittelun Lista
, koska java.util.Päiväys
on supertyyppi java.sql.Päiväys
:
Lista sqlList = ...; lajittelu (sqlList, uusi DateComparator ());
Jos jätämme ristiriitaisuuden ohjelmassa järjestellä()
allekirjoitus (vain tai määrittelemätön, vaarallinen
), kääntäjä hylkää viimeisen rivin tyypin virheenä.
Soittaaksesi
lajittelu (sqlList, uusi SqlDateComparator ());
sinun pitäisi kirjoittaa ylimääräinen erikoislaatuinen luokka:
class SqlDateComparator laajentaa DateComparatoria {}
Lisämenetelmät
Collections.sort ()
ei ole ainoa Java Collections -sovellusliittymämenetelmä, joka on varustettu kiistanalaisella parametrilla. Menetelmät, kuten addAll ()
, binaarihaku ()
, kopio()
, täyttää()
, ja niin edelleen, voidaan käyttää samanlaisella joustavuudella.
Kokoelmat
menetelmiä, kuten enintään ()
ja min ()
tarjoavat sopivia tulostyyppejä:
julkinen staattinen T max (kokoelmakokoelma) {...}
Kuten näette täällä, tyypin parametria voidaan pyytää täyttämään useampi kuin yksi ehto, vain käyttämällä &
. ulottuu Object
saattaa tuntua tarpeettomalta, mutta siinä määrätään enintään ()
palauttaa tyypin tuloksen Esine
eikä rivistä Vertailukelpoinen
tavukoodissa. (Tavutekoodissa ei ole tyypin parametreja.)
Ylikuormitettu versio enintään ()
kanssa Vertailija
on vielä hauskempaa:
public staattinen T max (kokoelmakokoelma, Comparator comp)
Tämä enintään ()
on molemmat sopivia ja kovariaattityyppiset parametrit. Vaikka elementit Kokoelma
on oltava (mahdollisesti erilaisia) tietyn tyyppisiä (ei nimenomaisesti annettuja) alatyyppejä, Vertailija
täytyy olla instanssoitu samantyyppiselle supertyypille. Kääntäjän päättelyalgoritmilta vaaditaan paljon tämän välisen tyypin erottamiseksi tällaisesta puhelusta:
Kokoelmakokoelma = ...; Vertailuvertailija = ...; max (kokoelma, vertailija);
Tyyppiparametrien laatikoihin sidonta
Viimeisenä esimerkkinä tyypin riippuvuudesta ja varianssista Java Collections -sovellusliittymässä harkitaan uudelleen järjestellä()
kanssa Vertailukelpoinen
. Huomaa, että se käyttää molempia ulottuu
ja super
, jotka ovat laatikoissa:
staattinen void sort (luetteloluettelo) {...}
Tässä tapauksessa emme ole yhtä kiinnostuneita viitteiden yhteensopivuudesta kuin sitomalla instansointia. Tämä järjestellä()
menetelmä lajittelee a lista
objekti, jonka luokan elementit toteuttavat Vertailukelpoinen
. Useimmissa tapauksissa lajittelu toimisi ilman menetelmän allekirjoituksessa:
lajittelu (dateList); // java.util.Date toteuttaa Vertailukelpoinen lajittelu (sqlList); // java.sql.Date toteuttaa Vertailukelpoinen
Tyyppiparametrin alaraja antaa kuitenkin lisää joustavuutta. Vertailukelpoinen
ei välttämättä tarvitse toteuttaa elementtiluokassa; riittää, kun olet ottanut sen käyttöön superluokassa. Esimerkiksi:
luokka SuperClass toteuttaa Vertailukelpoinen {public int CompareTo (SuperClass s) {...}} -luokan aliluokka laajentaa SuperClassia {} // ilman ylikuormitusta vertailulle () List superList = ...; lajittelu (superList); Lista alilista = ...; lajittelu (alilista);
Kääntäjä hyväksyy viimeisen rivin
staattinen void sort (luetteloluettelo) {...}
ja hylkää sen
staattinen void sort (luetteloluettelo) {...}
Syynä hylkäämiseen on tyyppi Alaluokka
(jonka kääntäjä määrittäisi tyypin perusteella Lista
parametrissa alilista
) ei sovi tyypin parametriksi T ulottuu vertailukelpoiseksi
. Tyyppi Alaluokka
ei toteuta Vertailukelpoinen
; se vain toteuttaa Vertailukelpoinen
. Nämä kaksi elementtiä eivät ole yhteensopivia implisiittisen kovarianssin puuttumisen vuoksi Alaluokka
on yhteensopiva SuperClass
.
Toisaalta, jos käytämme , kääntäjä ei odota
Alaluokka
toteuttaa Vertailukelpoinen
; riittää jos SuperClass
tekee sen. Se riittää, koska menetelmä vertaa()
on peritty SuperClass
ja sitä voidaan pyytää Alaluokka
esineet: ilmaisee tämän, aiheuttaen ristiriitaisuutta.
Tyyppiparametrin käyttöoikeusmuuttujat
Ylä- tai alaraja koskee vain type -parametri kovariaanisen tai ristiriitaisen viittauksen osoittamat esimerkit. Siinä tapauksessa että Generic covariantReference;
ja Yleinen sopivaReferenssi;
, voimme luoda ja viitata eri kohteisiin Yleinen
ilmentymät.
Menetelmän parametrille ja tulostyypille (kuten kohteelle tulo ja ulostulo yleisen tyypin parametrityypit). Mielivaltainen objekti, joka on yhteensopiva Alatyyppi
voidaan välittää menetelmän parametrina kirjoittaa()
, kuten edellä on määritelty.
contravariantReference.write (uusi SubType ()); // OK contravariantReference.write (uusi SubSubType ()); // OK liian contravariantReference.write (uusi SuperType ()); // tyypin virhe ((Generic) contravariantReference) .write (new SuperType ()); // OK
Ristiriitojen takia on mahdollista välittää parametri kirjoittaa()
. Tämä on ristiriidassa kovariaanisen (myös rajattoman) yleismerkityypin kanssa.
Tulostyypin tilanne ei muutu sitomalla: lukea()
tuottaa edelleen tyypin tuloksen ?
, yhteensopiva vain Esine
:
Objekti o = contravariantReference.read (); SubType st = contravariantReference.read (); // tyyppivirhe
Viimeinen rivi tuottaa virheen, vaikka olemme ilmoittaneet a Viittaus
tyypin Yleinen
.
Tulostyyppi on yhteensopiva toisen tyypin kanssa vain jälkeen viitetyyppi on muunnettu nimenomaisesti:
SuperSuperType sst = ((yleinen) contravariantReference) .luku (); sst = (SuperSuperType) contravariantReference.read (); // vaarallinen vaihtoehto
Esimerkit edellisistä luetteloista osoittavat, että luku- tai kirjoitusoikeus tyypin muuttujaan parametri
käyttäytyy samalla tavalla riippumatta siitä, tapahtuuko menetelmä (luku ja kirjoitus) vai suoraan (esimerkkien tiedot).
Tyyppiparametrin muuttujien lukeminen ja kirjoittaminen
Taulukko 1 osoittaa, että lukeminen Esine
muuttuja on aina mahdollista, koska jokainen luokka ja jokerimerkki ovat yhteensopivia Esine
. Kirjoittaminen Esine
on mahdollista vain sopivan heijastuksen jälkeen asianmukaisen valamisen jälkeen, koska Esine
ei ole yhteensopiva jokerimerkin kanssa. Lukeminen ilman heittämistä sopimattomaan muuttujaan on mahdollista kovariaanisen viitteen avulla. Kirjoittaminen on mahdollista kiistanalaisella viitteellä.
Taulukko 1. Tyyppiparametrin muuttujien lukeminen ja kirjoittaminen
käsittelyssä (syöttö) | lukea Esine | kirjoittaa Esine | lukea supertyyppi | kirjoittaa supertyyppi | lukea alatyyppi | kirjoittaa alatyyppi |
Jokerimerkki
| OK | Virhe | Heittää | Heittää | Heittää | Heittää |
Kovaria
| OK | Virhe | OK | Heittää | Heittää | Heittää |
Suonensisäinen
| OK | Heittää | Heittää | Heittää | Heittää | OK |
Taulukon 1 rivit viittaavat eräänlainen viiteja sarakkeet tietojen tyyppi olla käytettävissä. Otsikot "supertype" ja "subtype" osoittavat jokerimerkkejä. Merkintä "valettu" tarkoittaa, että viite on valettava. Esimerkki "OK" neljässä viimeisessä sarakkeessa viittaa kovariaation ja ristiriitaisuuden tyypillisiin tapauksiin.
Katso tämän artikkelin lopusta taulukon systemaattinen testausohjelma, jossa on yksityiskohtaiset selitykset.
Objektien luominen
Toisaalta et voi luoda jokerimerkkityyppejä, koska ne ovat abstrakteja. Toisaalta voit luoda vain rajoittamattoman yleismerkkityypin taulukko-objekteja. Et kuitenkaan voi luoda muita yleisiä esimerkkejä.
Generic [] genericArray = uusi Generic [20]; // tyypin virhe Generic [] wildcardArray = new Generic [20]; // OK genericArray = (Generic []) wildcardArray; // tarkistamaton muunnos genericArray [0] = new Generic (); genericArray [0] = uusi yleinen (); // tyyppi virhe wildcardArray [0] = uusi yleinen (); // OK
Taulukoiden kovarianssista johtuen jokerimerkkijonotyyppi Yleinen []
on kaikkien ilmentymien taulukotyypin supertyyppi; sen vuoksi yllä olevan koodin viimeisellä rivillä osoittaminen on mahdollista.
Yleisessä luokassa emme voi luoda tyypin parametrin objekteja. Esimerkiksi rakentajan ArrayList
toteutus, taulukko-objektin on oltava tyyppiä Esine[]
luomisen jälkeen. Voimme sitten muuntaa sen tyyppiparametrin taulukotyypiksi:
luokka MyArrayList toteuttaa luettelon {private final E [] sisältöä; MyArrayList (int-koko) {content = new E [size]; // type error content = (E []) new Object [size]; // kiertotapa} ...}
Saat turvallisemman kiertotavan ohittamalla Luokka
todellisen tyypin parametrin arvo rakentajalle:
sisältö = (E []) java.lang.reflect.Array.newInstance(myClass, koko);
Useita tyypin parametreja
Geneerisellä tyypillä voi olla useampi kuin yksi tyypin parametri. Tyyppiparametrit eivät muuta kovarianssin ja ristiriitaisuuden käyttäytymistä, ja useita tyypin parametreja voi esiintyä yhdessä, kuten alla on esitetty:
luokan G {} G viite; viite = uusi G (); // ilman varianssiviitettä = uusi G (); // yhdessä- ja ristiriitaisuudella
Yleinen käyttöliittymä java.util.Kartta
käytetään usein esimerkkinä monityyppisille parametreille. Käyttöliittymässä on kaksi tyyppiparametriä, yksi avaimelle ja toinen arvolle. On hyödyllistä liittää objekteja esimerkiksi avaimiin, jotta voimme löytää ne helpommin. Puhelinluettelo on esimerkki a Kartta
objekti, joka käyttää useita tyyppisiä parametreja: tilaajan nimi on avain, puhelinnumero on arvo.
Käyttöliittymän toteutus java.util.HashMap
on konstruktori mielivaltaisen muuntamiseksi Kartta
objekti assosiaatiotaulukkoon:
julkinen HashMap (kartta m) ...
Kovarianssin takia parametriobjektin tyypin parametrin ei tarvitse tässä tapauksessa vastata tarkkaa tyypin parametriluokkaa K
ja V
. Sen sijaan se voidaan mukauttaa kovarianssilla:
Kartta-asiakkaat; ... yhteystiedot = uusi HashMap (asiakkaat); // kovariantti
Tässä, Id
on supertyyppi Asiakasnumero
ja Henkilö
on supertyyppi Asiakas
.
Menetelmien vaihtelu
Olemme puhuneet tyyppien varianssista; nyt siirrytään hieman helpompaan aiheeseen.