Ohjelmointi

Tyyppiriippuvuus Java-ohjelmassa, osa 1

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 T1tai 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 Esineesimerkiksi siksi, että kaikki luokat perivät implisiittisesti Esine. Kokonaisluku ei ole yhteensopiva Kelluakuitenkin, koska Kellua ei ole ryhmän superluokka Kokonaisluku. KokonaislukuOn 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.

Andreas Solymosi

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.

Andreas Solymosi

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.

Andreas Solymosi

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.

Andreas Solymosi

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 Usitten 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 Alatyyppija 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 Solymosi

Muista, 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ä.

$config[zx-auto] not found$config[zx-overlay] not found