Razumevanje združljivosti tipov je bistvenega pomena za pisanje dobrih programov Java, toda medsebojno vplivanje na razlike med elementi jezika Java se lahko nepoznavalcem zdi zelo akademsko. Ta članek je namenjen razvijalcem programske opreme, ki so pripravljeni spoprijeti se z izzivom! Prvi del razkriva kovariante in kontravariantne odnose med enostavnejšimi elementi, kot so tipi matrike in generični tipi, pa tudi posebni element jezika Java, nadomestni znak. Drugi del raziskuje odvisnost in varianto tipa v pogostih primerih API in v lambda izrazih.
prenos Prenos vira Pridobite izvorno kodo za ta članek, "Odvisnost od tipa v Javi, 1. del". Za JavaWorld ustvaril dr. Andreas Solymosi.Pojmi in terminologija
Preden se lotimo odnosov med kovarianco in kontravariancem med različnimi elementi jezika Java, se prepričajmo, da imamo skupen konceptualni okvir.
Kompatibilnost
Pri objektno usmerjenem programiranju kompatibilnost se nanaša na usmerjeno razmerje med vrstami, kot je prikazano na sliki 1.
Andreas Solymosi Pravimo, da sta dve vrsti združljiv v Javi, če je mogoče prenos podatkov med spremenljivkami vrst. Prenos podatkov je mogoč, če ga prevajalnik sprejme in se opravi z dodeljevanjem ali posredovanjem parametrov. Kot primer lahko kratek
je združljiv z int
ker naloga intVariable = shortVariable;
mogoče. Ampak logično
ni združljiv z int
ker naloga intVariable = booleanVariable;
ni mogoče; prevajalnik tega ne bo sprejel.
Ker je združljivost včasih usmerjen odnos T1
je združljiv z T2
ampak T2
ni združljiv z T1
, ali ne na enak način. To bomo videli še naprej, ko bomo razpravljali o eksplicitni ali implicitni združljivosti.
Pomembno je, da je možna združljivost med referenčnimi vrstami samo znotraj hierarhije tipov. Vsi tipi razredov so združljivi z Predmet
na primer zato, ker vsi razredi implicitno podedujejo od Predmet
. Celo število
ni združljiv z Float
pa zato Float
ni superrazred Celo število
. Celo število
je združljiv z Številka
, Ker Številka
je (abstraktni) superrazred Celo število
. Ker se nahajajo v isti tipični hierarhiji, prevajalnik sprejme nalogo numberReference = integerReference;
.
Govorimo o implicitno ali izrecno združljivost, odvisno od tega, ali mora biti združljivost označena izrecno ali ne. Na primer, kratka je implicitno združljiv z int
(kot je prikazano zgoraj), ne pa tudi obratno: naloga shortVariable = intVariable;
ni mogoča. Vendar kratek je izrecno združljiv z int
, ker je naloga shortVariable = (kratko) intVariable;
mogoče. Tu moramo združljivost označiti z litje, znano tudi kot pretvorba tipa.
Podobno med referenčnimi vrstami: integerReference = numberReference;
ni sprejemljivo, samo integerReference = (Integer) numberReference;
bi bila sprejeta. Zato Celo število
je implicitno združljiv z Številka
ampak Številka
je samo izrecno združljiv z Celo število
.
Odvisnost
Tip je lahko odvisen od drugih vrst. Na primer vrsta matrike int []
odvisno od primitivnega tipa int
. Podobno tudi generični tip ArrayList
je odvisno od vrste Stranka
. Metode so lahko odvisne tudi od tipa, odvisno od vrste njihovih parametrov. Na primer metoda prirastek praznine (celo število i)
; odvisno od vrste Celo število
. Nekatere metode (na primer nekatere splošne vrste) so odvisne od več vrst - na primer metode z več kot enim parametrom.
Kovarianca in kontravarianca
Kovarianca in kontravarianca določata združljivost glede na vrste. V obeh primerih je varianca usmerjena relacija. Kovarianca lahko prevedemo kot "različni v isti smeri," oz s-drugačno, medtem ko kontravarenca pomeni "drugače v nasprotni smeri", oz proti-drugačnim. Kovariantni in kontravariantni vrsti nista enaki, vendar obstaja povezava med njima. Imena kažejo smer korelacije.
Torej, kovarianca pomeni, da združljivost dveh vrst pomeni združljivost vrst, ki so odvisne od njih. Glede na združljivost tipov predpostavljamo, da so odvisni tipi kovarianti, kot je prikazano na sliki 2.
Andreas Solymosi Združljivost T1
do T2
pomeni združljivost A (T1
) do A (T2
). Odvisni tip A (T)
je poklican kovariantno; ali natančneje, A (T1
) je kovarijantna na A (T2
).
Za drug primer: ker je naloga numberArray = integerArray;
(vsaj v Javi) vrste matrike Celo število []
in Številka []
so kovariante. Torej, to lahko rečemo Celo število []
je implicitno kovarianten do Številka []
. In čeprav nasprotno ne drži - naloga integerArray = numberArray;
ni mogoče - dodelitev s tipskim litjem (integerArray = (Celo število []) numberArray;
) je mogoče; zato pravimo, Številka []
je izrecno kovarianten do Celo število []
.
Povzeti: Celo število
je implicitno združljiv z Številka
, torej Celo število []
je implicitno kovarianten na Številka []
, in Številka []
je izrecno kovarijantna na Celo število []
. Slika 3 prikazuje.
Na splošno lahko rečemo, da so vrste nizov v Java kovariante. V nadaljevanju članka si bomo ogledali primere kovariancije med generičnimi vrstami.
Kontravirnost
Tako kot kovarianca je tudi kontravarianca a usmerjeno razmerje. Medtem ko kovarianca pomeni s-drugačno, kontravariance pomeni proti-drugačnim. Kot sem že omenil, imena izražajo smer korelacije. Pomembno je tudi opozoriti, da varianca na splošno ni atribut tipov, temveč le odvisna vrste (kot so nizi in generični tipi, pa tudi metode, ki jih bom obravnaval v 2. delu).
Odvisen tip, kot je A (T)
je poklican kontravariantno če je združljivost T1
do T2
pomeni združljivost A (T2
) do A (T1
). Slika 4 prikazuje.
Jezikovni element (vrsta ali metoda) A (T)
odvisno od T
je kovariantno če je združljivost T1
do T2
pomeni združljivost A (T1
) do A (T2
). Če je združljivost T1
do T2
pomeni združljivost A (T2
) do A (T1
), nato tip A (T)
je kontravariantno. Če je združljivost T1
med T2
ne pomeni nobene združljivosti med A (T1
) in A (T2
), potem A (T)
je nespremenljivo.
Vrste matrike v Javi niso implicitno kontravariantno, vendar so lahko izrecno kontravariantno , tako kot generične vrste. V nadaljevanju članka bom ponudil nekaj primerov.
Elementi, odvisni od tipa: metode in tipi
V Javi so metode, tipi matrike in splošni (parametrizirani) tipi od tipa odvisni elementi. Metode so odvisne od vrste njihovih parametrov. Vrsta matrike, T []
, je odvisno od vrste njegovih elementov, T
. Splošni tip G
je odvisen od njegovega parametra tipa, T
. Slika 5 prikazuje.
Ta članek se večinoma osredotoča na združljivost vrst, čeprav se bom dotaknil združljivosti med metodami proti koncu 2. dela.
Implicitna in eksplicitna združljivost tipa
Prej ste videli tip T1
biti implicitno (ali izrecno) združljiv z T2
. To velja le, če je dodeljena spremenljivka tipa T1
na spremenljivko tipa T2
je dovoljeno brez (ali z) označevanjem. Predvajanje tipov je najpogostejši način označevanja eksplicitne združljivosti:
variableOfTypeT2 = variableOfTypeT1; // implicitno združljiva variableOfTypeT2 = (T2) variableOfTypeT1; // izrecno združljivo
Na primer, int
je implicitno združljiv z dolga
in izrecno združljiv z kratek
:
int intVariable = 5; long longVariable = intVariable; // implicitno združljiv kratki shortVariable = (kratek) intVariable; // izrecno združljivo
Implicitna in eksplicitna združljivost obstaja ne le pri dodelitvah, temveč tudi pri prenosu parametrov iz klica metode v definicijo metode in nazaj. Skupaj z vhodnimi parametri to pomeni tudi posredovanje rezultata funkcije, kar bi naredili kot izhodni parameter.
Upoštevajte to logično
ni združljiv z nobeno drugo vrsto, prav tako pa tudi primitivna in referenčna vrsta ne moreta biti združljivi.
Parametri metode
Pravimo, da metoda bere vhodne parametre in zapisuje izhodne parametre. Parametri primitivnih tipov so vedno vhodni parametri. Vrnjena vrednost funkcije je vedno izhodni parameter. Parametra referenčnih vrst sta lahko oba: če metoda spremeni referenco (ali primitivni parameter), sprememba ostane znotraj metode (kar pomeni, da po klicu ni vidna zunaj metode - to je znano kot klic po vrednosti). Če metoda spremeni navedeni objekt, pa sprememba ostane po vrnitvi iz metode - to je znano kot klic po referenci.
(Referenčni) podtip je implicitno združljiv s svojim nadtipom, nadtip pa izrecno združljiv s svojim podtipom. To pomeni, da so referenčni tipi združljivi samo znotraj svoje veje hierarhije - implicitno navzgor in eksplicitno navzdol:
referenceOfSuperType = referenceOfSubType; // implicitno združljiv referenceOfSubType = (SubType) referenceOfSuperType; // izrecno združljivo
Prevajalnik Java običajno dovoljuje implicitno združljivost naloge samo če ni nevarnosti, da bi med izvajanjem med različnimi vrstami izgubili podatke. (Upoštevajte pa, da to pravilo ne velja za izgubo natančnosti, na primer pri nalogi iz int
plavati.) Na primer, int
je implicitno združljiv z dolga
ker a dolga
spremenljivka vsebuje vsak int
vrednost. Nasprotno pa a kratek
spremenljivka ne vsebuje nobene int
vrednote; tako je med temi elementi dovoljena le izrecna združljivost.
Upoštevajte, da implicitna združljivost na sliki 6 predpostavlja, da je razmerje prehodno: kratek
je združljiv z dolga
.
Podobno kot vidite na sliki 6, je vedno mogoče dodeliti sklic na podtip int
sklic na nadtip. Upoštevajte, da bi lahko enaka naloga v drugo smer vrgla a ClassCastException
vendar ga prevajalnik Java dovoljuje le z ulivanjem vrst.
Kovarianca in kontravariance za vrste matrike
V Javi so nekatere vrste nizov kovarijantne in / ali kontravariantne. V primeru kovariacije to pomeni, da če T
je združljiv z U
, potem T []
je tudi združljiv z U []
. V primeru kontravariance to pomeni U []
je združljiv z T []
. Nizi primitivnih vrst so v Java invariantni:
longArray = intArray; // napaka tipa shortArray = (kratko []) intArray; // napaka tipa
Nizov referenčnih vrst je implicitno kovarianten in izrecno kontravariantno, vendar:
SuperType [] superArray; SubType [] subArray; ... superArray = subArray; // implicitni kovariantni podArray = (SubType []) superArray; // eksplicitna kontravarianta
Andreas Solymosi Slika 7. Implicitna kovarianca za nize
To v praksi pomeni, da lahko dodelitev komponent matrike vrže ArrayStoreException
med izvajanjem. Če je sklic na matriko SuperType
se sklicuje na objekt matrike Podtip
in ena od njegovih komponent se nato dodeli a SuperType
predmet, nato:
superArray [1] = nov SuperType (); // vrže ArrayStoreException
Temu včasih rečejo problem kovariacije. Resnična težava ni toliko izjema (ki bi se ji lahko izognili s programiranjem), temveč to, da mora navidezni stroj med izvajanjem preveriti vsako dodelitev v elementu polja. To postavlja Javo v slabši izkoristek glede na jezike brez kovariancije (kjer je prepovedana združljiva dodelitev referenc na nize) ali jezike, kot je Scala, kjer je kovarianco mogoče izklopiti.
Primer za kovarianco
V preprostem primeru je sklic na vrsto tipa Predmet []
vendar so objekt matrike in elementi različnih razredov:
Object [] objectArray; // sklic na matriko objectArray = nov niz [3]; // objekt matrike; združljiva dodelitev objectArray [0] = novo celo število (5); // vrže ArrayStoreException
Zaradi kovariacije prevajalnik ne more preveriti pravilnosti zadnje dodelitve elementom matrike - JVM to naredi in to z velikimi stroški. Vendar lahko prevajalnik optimizira stroške, če med vrstami matrike ni uporabljena združljivost tipov.
Andreas SolymosiNe pozabite, da je v Javi za referenčno spremenljivko neke vrste, ki se sklicuje na objekt svojega nadtipa, prepovedano: puščice na sliki 8 ne smejo biti usmerjene navzgor.
Razlike in nadomestni znaki v generičnih vrstah
Splošni (parametrizirani) tipi so implicitno invariantno v Javi, kar pomeni, da različni primerki generičnega tipa med seboj niso združljivi. Tudi ulivanje vrst ne bo prineslo združljivosti:
Generični superGeneric; Generični podGeneric; subGeneric = (Generic) superGeneric; // napaka tipa superGeneric = (Generic) subGeneric; // napaka tipa
Napake tipa se pojavijo, čeprav subGeneric.getClass () == superGeneric.getClass ()
. Težava je v tem, da metoda getClass ()
določa neobdelani tip - zato parameter tipa ne spada v podpis metode. Tako dve deklaraciji metode
metoda praznine (generični p); metoda praznine (generični p);
ne smejo nastopati skupaj v definiciji vmesnika (ali abstraktnega razreda).