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ä artikkeli on tarkoitettu ohjelmistokehittäjille, jotka ovat valmiita vastaamaan haasteeseen! Osa 1 paljastaa kovariaaniset ja ristiriitaiset suhteet yksinkertaisempien elementtien, kuten taulukotyyppien ja yleisten tyyppien, sekä erityisen Java-kielielementin, jokerin, välillä. Osa 2 tutkii tyypin riippuvuutta ja varianssia yleisissä API-esimerkeissä ja lambda-lausekkeissa.
lataa lähde Lataa lähde Hanki tämän artikkelin "Tyyppiriippuvuus Javassa, osa 1" lähdekoodi. Luonut JavaWorldille tohtori Andreas Solymosi.Käsitteet ja terminologia
Ennen kuin tutustumme kovarianssin ja ristiriitaisuuden suhteisiin erilaisten Java-kielielementtien välillä, varmista, että meillä on yhteinen käsitteellinen kehys.
Yhteensopivuus
Kohdekeskeisessä ohjelmoinnissa yhteensopivuus viittaa suunnattuun suhteeseen tyyppien välillä, kuten kuvassa 1 on esitetty.
Andreas Solymosi Sanomme, että kahta tyyppiä on yhteensopiva Java-tilassa, jos on mahdollista siirtää tietoja tyypin muuttujien välillä. Tiedonsiirto on mahdollista, jos kääntäjä hyväksyy sen ja tapahtuu tehtävän tai parametrin välityksellä. Esimerkiksi, lyhyt
on yhteensopiva int
koska tehtävä intVariable = lyhytVariable;
on mahdollista. Mutta looginen
ei ole yhteensopiva int
koska tehtävä intVariable = looginenVariable;
ei ole mahdollista; kääntäjä ei hyväksy sitä.
Koska yhteensopivuus on joskus suunnattu suhde T1
on yhteensopiva T2
mutta T2
ei ole yhteensopiva T1
tai ei samalla tavalla. Näemme tämän edelleen, kun keskustelemme nimenomaisesta tai epäsuorasta yhteensopivuudesta.
Tärkeää on, että vertailutyyppien yhteensopivuus on mahdollista vain tyyppihierarkiassa. Kaikki luokkatyypit ovat yhteensopivia Esine
esimerkiksi siksi, että kaikki luokat perivät implisiittisesti Esine
. Kokonaisluku
ei ole yhteensopiva Kellua
kuitenkin, koska Kellua
ei ole ryhmän superluokka Kokonaisluku
. Kokonaisluku
On yhteensopiva Määrä
, koska Määrä
on (abstrakti) ryhmän Kokonaisluku
. Koska kääntäjät sijaitsevat samantyyppisessä hierarkiassa, kääntäjä hyväksyy tehtävän numberReference = kokonaislukuReference;
.
Me puhumme implisiittinen tai nimenomainen yhteensopivuus sen mukaan, onko yhteensopivuus merkittävä erikseen vai ei. Esimerkiksi lyhyt on epäsuorasti yhteensopiva int
(kuten yllä on esitetty), mutta ei päinvastoin: tehtävä shortVariable = vaihteleva;
ei ole mahdollista. Lyhyt on kuitenkin nimenomaisesti yhteensopiva int
, koska tehtävä shortVariable = (lyhyt) muuttuja;
on mahdollista. Tässä on merkittävä yhteensopivuus valu, joka tunnetaan myös nimellä tyyppimuunnos.
Vastaavasti vertailutyyppien joukossa: integerReference = numeroReference;
ei ole hyväksyttävää integerReference = (Kokonaisluku) numberReference;
hyväksytään. Siksi, Kokonaisluku
On epäsuorasti yhteensopiva Määrä
mutta Määrä
on vain nimenomaisesti yhteensopiva Kokonaisluku
.
Riippuvuus
Tyyppi voi riippua muista tyypeistä. Esimerkiksi taulukon tyyppi int []
riippuu primitiivisestä tyypistä int
. Samoin yleinen tyyppi ArrayList
riippuu tyypistä Asiakas
. Menetelmät voivat myös olla tyypistä riippuvia niiden parametrien tyypistä riippuen. Esimerkiksi menetelmä mitätön lisäys (kokonaisluku i)
; riippuu tyypistä Kokonaisluku
. Jotkut menetelmät (kuten jotkut yleiset tyypit) riippuvat useammasta kuin yhdestä tyypistä - kuten menetelmistä, joissa on enemmän kuin yksi parametri.
Kovarianssi ja ristiriita
Kovarianssi ja ristiriita määrittävät yhteensopivuuden tyyppien perusteella. Kummassakin tapauksessa varianssi on suunnattu suhde. Kovarianssi voidaan kääntää "erilaisiksi samaan suuntaan" tai kanssa-erilainen, kun taas ristiriita tarkoittaa "erilaista vastakkaiseen suuntaan" tai vastaan-erilainen. Kovariaattiset ja ristiriitaiset tyypit eivät ole samat, mutta niiden välillä on korrelaatio. Nimet viittaavat korrelaation suuntaan.
Niin, kovarianssi tarkoittaa, että kahden tyyppinen yhteensopivuus tarkoittaa niistä riippuvien tyyppien yhteensopivuutta. Kun otetaan huomioon tyyppien yhteensopivuus, voidaan olettaa, että riippuvat tyypit ovat vaihtelevia, kuten kuvassa 2 on esitetty.
Andreas Solymosi Yhteensopivuus T1
että T2
tarkoittaa yhteensopivuutta A (T1
) A (T2
). Riippuva tyyppi A (T)
kutsutaan kovaria; tai tarkemmin sanottuna A (T1
) on vaihteleva A (T2
).
Toinen esimerkki: koska tehtävä numberArray = kokonaisluku Array;
on mahdollista (ainakin Java-muodossa), taulukotyypit Kokonaisluku[]
ja Määrä[]
ovat kovariaanisia. Joten voimme sanoa sen Kokonaisluku[]
On implisiittisesti kovariantti että Määrä[]
. Ja vaikka päinvastoin ei ole totta - tehtävä kokonaislukuArray = lukuArray;
ei ole mahdollista - tehtävä tyypin valulla (integerArray = (Kokonaisluku []) numeroArray;
) On mahdollista; siksi me sanomme, Määrä[]
On nimenomaan kovariaaninen että Kokonaisluku[]
.
Yhteenvetona: Kokonaisluku
on implisiittisesti yhteensopiva Määrä
, siksi Kokonaisluku[]
on implisiittisesti covariant Määrä[]
ja Määrä[]
on nimenomaisesti mukautuva Kokonaisluku[]
. Kuva 3 kuvaa.
Yleisesti ottaen voidaan sanoa, että matriisityypit vaihtelevat Javassa. Seuraavassa artikkelissa tarkastellaan esimerkkejä kovarianssista geneeristen tyyppien joukossa.
Ristiriitaisuus
Kovarianssin tavoin ristiriitaisuus on a ohjattu suhde. Kovariaatio tarkoittaa kanssa-erilainen, ristiriitaisuus tarkoittaa vastaan-erilainen. Kuten aiemmin mainitsin, nimet ilmaisevat korrelaation suunnan. On myös tärkeää huomata, että varianssi ei ole tyypin ominaisuus yleensä, vaan vain riippuvainen tyypit (kuten taulukot ja yleiset tyypit sekä menetelmät, joista keskustelen osassa 2).
Riippuva tyyppi, kuten A (T)
kutsutaan sopusointuinen jos yhteensopivuus T1
että T2
tarkoittaa yhteensopivuutta A (T2
) A (T1
). Kuva 4 kuvaa.
Kielielementti (tyyppi tai menetelmä) A (T)
riippuen T
On kovaria jos yhteensopivuus T1
että T2
tarkoittaa yhteensopivuutta A (T1
) A (T2
). Jos yhteensopivuus T1
että T2
tarkoittaa yhteensopivuutta A (T2
) A (T1
), sitten tyyppi A (T)
On sopusointuinen. Jos yhteensopivuus T1
välillä T2
ei tarkoita yhteensopivuutta A (T1
) ja A (T2
) A (T)
On muuttumaton.
Java-taulukotyypit eivät ole epäsuorasti ristiriitaisia, mutta ne voivat olla nimenomaisesti ristiriitaisia , aivan kuten yleiset tyypit. Annan joitain esimerkkejä myöhemmin artikkelissa.
Tyyppikohtaiset elementit: Menetelmät ja tyypit
Java-tilassa menetelmät, taulukotyypit ja yleiset (parametrisoidut) tyypit ovat tyypistä riippuvia elementtejä. Menetelmät riippuvat niiden parametrien tyypistä. Matriisityyppi, T []
, on riippuvainen sen elementtien tyypistä, T
. Geneerinen tyyppi G
on riippuvainen sen tyypin parametrista, T
. Kuva 5 kuvaa.
Enimmäkseen tässä artikkelissa keskitytään tyyppien yhteensopivuuteen, vaikka kosketan menetelmien yhteensopivuutta osan 2 loppupuolella.
Implisiittinen ja eksplisiittinen tyyppien yhteensopivuus
Aiemmin näit tyypin T1
oleminen epäsuorasti (tai nimenomaisesti) yhteensopiva T2
. Tämä on totta vain, jos tyypin muuttuja määritetään T1
tyypin muuttujaan T2
on sallittu ilman tunnisteita (tai niiden kanssa). Tyyppivalu on yleisin tapa tunnistaa nimenomainen yhteensopivuus:
variableOfTypeT2 = muuttujaOfTypeT1; // implisiittinen yhteensopiva muuttujaOfTypeT2 = (T2) muuttujaOfTypeT1; // nimenomaisesti yhteensopiva
Esimerkiksi, int
on implisiittisesti yhteensopiva pitkä
ja nimenomaisesti yhteensopiva lyhyt
:
int vaihtuva = 5; pitkä pitkäVaihteleva = vaihteleva; // implisiittinen yhteensopiva lyhyt shortVariable = (lyhyt) intVariable; // nimenomaisesti yhteensopiva
Implisiittinen ja eksplisiittinen yhteensopivuus ei ole vain tehtävissä, vaan myös parametrien välittämisessä menetelmän kutsusta menetelmän määrittelyyn ja takaisin. Yhdessä syöttöparametrien kanssa tämä tarkoittaa myös funktion tuloksen välittämistä, jonka tekisit lähtöparametrina.
Ota huomioon, että looginen
ei ole yhteensopiva minkään muun tyypin kanssa, eikä primitiivinen ja viitetyyppi voi koskaan olla yhteensopiva.
Menetelmän parametrit
Sanomme, menetelmä lukee syöttöparametrit ja kirjoittaa tulosparametrit. Primitiivisten tyyppien parametrit ovat aina syöttöparametreja. Funktion paluuarvo on aina lähtöparametri. Viitetyyppien parametrit voivat olla molemmat: jos menetelmä muuttaa viitettä (tai primitiivistä parametria), muutos pysyy menetelmän sisällä (eli se ei ole näkyvissä menetelmän ulkopuolella puhelun jälkeen - tämä tunnetaan nimellä soita arvon mukaan). Jos menetelmä muuttaa viitattua objektia, muutos kuitenkin pysyy palautettuna menetelmästä - tämä tunnetaan nimellä soita viitteenä.
(Viite) -alatyyppi on implisiittisesti yhteensopiva sen supertyypin kanssa ja supertyyppi on nimenomaisesti yhteensopiva sen alatyypin kanssa. Tämä tarkoittaa, että viitetyypit ovat yhteensopivia vain niiden hierarkiahaarassa - implisiittisesti ylöspäin ja nimenomaisesti alaspäin:
referenceOfSuperType = referenceOfSubType; // implisiittinen yhteensopiva referenceOfSubType = (SubType) referenceOfSuperType; // nimenomaisesti yhteensopiva
Java-kääntäjä sallii tyypillisesti implisiittisen yhteensopivuuden tehtävälle vain jos ei ole vaaraa menettää tietoa ajon aikana eri tyyppien välillä. (Huomaa kuitenkin, että tämä sääntö ei ole kelvollinen menetettäessä tarkkuutta, kuten tehtävässä int
kellua.) Esimerkiksi int
on implisiittisesti yhteensopiva pitkä
koska a pitkä
muuttuja pitää jokaisen int
arvo. Sen sijaan a lyhyt
muuttuja ei sisällä yhtään int
arvot; siten näiden elementtien välillä sallitaan vain nimenomainen yhteensopivuus.
Huomaa, että kuvan 6 implisiittinen yhteensopivuus olettaa, että suhde on transitiivinen: lyhyt
on yhteensopiva pitkä
.
Samalla tavalla kuin näet kuvassa 6, on aina mahdollista määrittää alatyypin viite int
viittaus supertyyppiin. Muista, että sama tehtävä toiseen suuntaan voi heittää a ClassCastException
, joten Java-kääntäjä sallii sen vain tyyppivalulla.
Kovarianssi ja ristiriita ryhmätyypeille
Javassa jotkut matriisityypit ovat kovariaanisia ja / tai ristiriitaisia. Kovarianssin tapauksessa tämä tarkoittaa, että jos T
on yhteensopiva U
sitten T []
on myös yhteensopiva U []
. Ristiriitojen tapauksessa se tarkoittaa sitä U []
on yhteensopiva T []
. Primitiivisten tyyppien taulukot ovat Java-tilassa muuttumattomia:
longArray = intArray; // tyypin virhe shortArray = (lyhyt []) intArray; // tyyppivirhe
Viitetyyppien taulukot ovat implisiittisesti kovariantti ja nimenomaisesti ristiriitaisia, kuitenkin:
SuperType [] superArray; SubType [] subArray; ... superArray = subArray; // implisiittinen kovariantti subArray = (SubType []) superArray; // nimenomainen sopusointuinen
Andreas Solymosi Kuva 7. Implisiittinen kovarianssi matriiseille
Tämä tarkoittaa käytännössä sitä, että ryhmän komponenttien määritys voi heittää ArrayStoreException
ajon aikana. Jos taulukon viite SuperType
viittaa taulukon objektiin Alatyyppi
ja yksi sen komponenteista osoitetaan sitten a: lle SuperType
esine, sitten:
superArray [1] = uusi SuperType (); // heittää ArrayStoreException
Tätä kutsutaan joskus kovarianssiongelma. Todellinen ongelma ei ole niinkään poikkeus (joka voitaisiin välttää ohjelmoinnin avulla), vaan se, että virtuaalikoneen on tarkistettava kaikki matriisielementin tehtävät ajon aikana. Tämä asettaa Java tehokkuuseen verrattuna kieliin, joissa ei ole kovarianssia (joissa yhteensopiva ryhmäviittausten määritys on kielletty) tai kieliin, kuten Scala, joissa kovarianssi voidaan kytkeä pois päältä.
Esimerkki kovarianssista
Yksinkertaisessa esimerkissä taulukon viite on tyypiltään Esine[]
mutta matriisiobjekti ja elementit ovat eri luokkia:
Object [] objectArray; // taulukon viiteobjektiArray = uusi merkkijono [3]; // taulukko-objekti; yhteensopiva tehtävä objectArray [0] = uusi kokonaisluku (5); // heittää ArrayStoreException
Kovariaation takia kääntäjä ei voi tarkistaa viimeisen matriisielementtien tehtävän oikeellisuutta - JVM tekee tämän ja huomattavalla kustannuksella. Kääntäjä voi kuitenkin optimoida kustannukset poispäin, jos taulukotyyppien välillä ei ole tyyppien yhteensopivuutta.
Andreas SolymosiMuista, että Java-ohjelmassa jonkin tyyppisen viitemuuttujan viittaaminen sen supertyypin objektiin on kielletty: kuvan 8 nuolia ei saa osoittaa ylöspäin.
Varianssit ja yleismerkit yleisissä tyypeissä
Yleisiä (parametrisoituja) tyyppejä ovat implisiittisesti muuttumaton Java-kielellä, mikä tarkoittaa, että erilaiset yleistyyppiset ilmentymät eivät ole yhteensopivia keskenään. Jopa tyyppivalu ei johda yhteensopivuuteen:
Geneerinen superGeneric; Geneerinen aligeneerinen; subGeneric = (yleinen) superGeneric; // tyypin virhe superGeneric = (Generic) subGeneric; // tyyppivirhe
Tyyppivirheet syntyvät vaikka subGeneric.getClass () == superGeneric.getClass ()
. Ongelmana on, että menetelmä getClass ()
määrittää raakatyypin - tämän vuoksi tyypin parametri ei kuulu menetelmän allekirjoitukseen. Siten nämä kaksi menetelmä-ilmoitusta
mitätön menetelmä (yleinen p); mitätön menetelmä (yleinen p);
ei saa esiintyä yhdessä rajapinnan (tai abstraktin luokan) määritelmässä.