Pochopenie kompatibility typov je pre písanie dobrých programov Java zásadné, ale vzájomné pôsobenie odchýlok medzi prvkami jazyka Java môže pre nezasvätených pôsobiť vysoko akademicky. Tento článok je určený pre vývojárov softvéru, ktorí sú pripravení zvládnuť túto výzvu! 1. časť odhaľuje kovariantné a kontrariantné vzťahy medzi jednoduchšími prvkami, ako sú typy polí a generické typy, ako aj špeciálny element jazyka Java, zástupný znak. Časť 2 skúma závislosť a rozptyl typov v bežných príkladoch API a vo výrazoch lambda.
download Stiahnite si zdroj Získajte zdrojový kód pre tento článok „Závislosť typu v jazyku Java, časť 1.“ Vytvoril pre JavaWorld Dr. Andreas Solymosi.Pojmy a terminológia
Predtým, ako sa dostaneme do vzťahov kovariancie a kontravarencie medzi rôznymi prvkami jazyka Java, uistite sa, že máme spoločný koncepčný rámec.
Kompatibilita
V objektovo orientovanom programovaní kompatibilita sa vzťahuje na riadený vzťah medzi typmi, ako je znázornené na obrázku 1.
Andreas Solymosi Hovoríme, že dva typy sú kompatibilný v Jave, ak je možné prenášať údaje medzi premennými typov. Prenos dát je možný, ak to kompilátor akceptuje, a je realizovaný priradením alebo odovzdaním parametra. Ako príklad, krátky
je kompatibilný s int
pretože zadanie intVariable = shortVariable;
je možné. ale boolean
nie je kompatibilný s int
pretože zadanie intVariable = booleanVariable;
nie je možné; kompilátor to neprijme.
Pretože kompatibilita je niekedy riadený vzťah T1
je kompatibilný s T2
ale T2
nie je kompatibilný s T1
, alebo nie rovnakým spôsobom. Uvidíme to ďalej, keď sa dostaneme k diskusii o explicitnej alebo implicitnej kompatibilite.
Dôležité je, že je možná kompatibilita medzi referenčnými typmi iba v rámci hierarchie typov. Všetky typy tried sú kompatibilné s Objekt
napríklad preto, že všetky triedy dedia implicitne z Objekt
. Celé číslo
nie je kompatibilný s Plavák
, však preto Plavák
nie je nadtrieda Celé číslo
. Celé číslo
je kompatibilný s Číslo
, pretože Číslo
je (abstraktná) nadtrieda Celé číslo
. Pretože sa nachádzajú v hierarchii rovnakého typu, kompilátor prijme priradenie numberReference = integerReference;
.
Hovoríme o implicitné alebo výslovne kompatibilita, v závislosti od toho, či musí byť kompatibilita výslovne označená alebo nie. Napríklad krátke je implicitne kompatibilný s int
(ako je uvedené vyššie), ale nie naopak: zadanie shortVariable = intVariable;
nie je možné. Krátka je však výslovne kompatibilný s int
, pretože zadanie shortVariable = (short) intVariable;
je možné. Tu musíme označiť kompatibilitu pomocou odlievanie, tiež známy ako prevod typu.
Podobne medzi referenčnými typmi: integerReference = numberReference;
je neprijateľné, iba integerReference = (Integer) numberReference;
bude prijatý. Preto Celé číslo
je implicitne kompatibilný s Číslo
ale Číslo
je len výslovne kompatibilný s Celé číslo
.
Závislosť
Typ môže závisieť od iných typov. Napríklad typ poľa int []
záleží na primitívnom type int
. Podobne aj generický typ ArrayList
je závislá od typu Zákazník
. Metódy môžu byť tiež závislé od typu, v závislosti od typov ich parametrov. Napríklad metóda prírastok void (celé číslo i)
; záleží od typu Celé číslo
. Niektoré metódy (napríklad niektoré všeobecné typy) závisia od viac ako jedného typu - napríklad metódy s viac ako jedným parametrom.
Kovariancia a kontravarencia
Kovariancia a kontrarariance určujú kompatibilitu na základe typov. V obidvoch prípadoch je variancia priamym vzťahom. Kovariancia možno preložiť ako „odlišné v rovnakom smere“, alebo s-rôznymi, keďže protirečivosť znamená „odlišné v opačnom smere“, alebo proti-rôzne. Kovovariantné a kontrariantné typy nie sú rovnaké, ale existuje medzi nimi korelácia. Názvy naznačujú smer korelácie.
Takže kovariancia znamená, že kompatibilita dvoch typov znamená kompatibilitu typov na nich závislých. Vzhľadom na kompatibilitu typov sa predpokladá, že závislé typy sú kovariantné, ako je znázornené na obrázku 2.
Andreas Solymosi Zlučiteľnosť T1
do T2
znamená kompatibilitu A (T.1
) až A (T.2
). Závislý typ A (T)
sa volá kovarianty; alebo presnejšie A (T.1
) je kovariančné s A (T.2
).
Pre ďalší príklad: pretože zadanie numberArray = integerArray;
je možné (minimálne v Jave) typy polí Celé číslo []
a Číslo []
sú kovariantné. Môžeme to teda povedať Celé číslo []
je implicitne kovariantný do Číslo []
. A hoci opak nie je pravdou - zadanie integerArray = numberArray;
nie je možné - zadanie s odlievaním typu (integerArray = (Integer []) numberArray;
) je možné; preto hovoríme: Číslo []
je výslovne kovariančné do Celé číslo []
.
Zhrnúť: Celé číslo
je implicitne kompatibilný s Číslo
, preto Celé číslo []
je implicitne kovariantný k Číslo []
a Číslo []
je výslovne kovariančný s Celé číslo []
. Obrázok 3 ilustruje.
Všeobecne možno povedať, že typy polí sú v Jave kovariantné. Na príklady kovariancie medzi generickými typmi sa pozrieme ďalej v článku.
Rozporuplnosť
Rovnako ako kovariancia, aj kontravariancia je smeroval vzťah. Zatiaľ čo kovariancia znamená s-rôznymi, rozpor znamená proti-rôzne. Ako som už spomenul, názvy vyjadrujú smer korelácie. Je tiež dôležité poznamenať, že variancia nie je atribútom typov všeobecne, ale iba typu závislý typy (napríklad polia a generické typy a tiež metódy, ktorým sa budem venovať v 2. časti).
Závislý typ ako napr A (T)
sa volá protikladný ak je kompatibilita T1
do T2
znamená kompatibilitu A (T.2
) až A (T.1
). Obrázok 4 ilustruje.
Jazykový prvok (typ alebo metóda) A (T)
záleží na T
je kovarianty ak je kompatibilita T1
do T2
znamená kompatibilitu A (T.1
) až A (T.2
). Ak je kompatibilita T1
do T2
znamená kompatibilitu A (T.2
) až A (T.1
), potom typ A (T)
je protikladný. Ak je kompatibilita T1
medzi T2
neznamená žiadnu kompatibilitu medzi A (T.1
) a A (T.2
), potom A (T)
je nemenný.
Typy polí v Jave nie sú implicitne v rozpore, ale môžu byť výslovne proti , rovnako ako všeobecné typy. Ďalej uvediem niekoľko príkladov v článku.
Prvky závislé od typu: Metódy a typy
V Jave sú metódy, typy polí a všeobecné (parametrizované) typy typovo závislé prvky. Metódy závisia od typu ich parametrov. Typ poľa, T []
, je závislá na druhoch jej prvkov, T
. Všeobecný typ G
je závislá na jeho parametri typu, T
. Obrázok 5 zobrazuje.
Tento článok sa väčšinou zameriava na kompatibilitu typov, aj keď sa budem na konci 2. časti zaoberať kompatibilitou medzi metódami.
Implicitná a explicitná kompatibilita typov
Skôr ste videli typ T1
bytie implicitne (alebo výslovne) kompatibilný s T2
. To platí iba v prípade, že ide o priradenie premennej typu T1
na premennú typu T2
je povolený bez označenia (alebo s). Cast casting je najbežnejším spôsobom označenia explicitnej kompatibility:
variableOfTypeT2 = variableOfTypeT1; // implicitná kompatibilná variableOfTypeT2 = (T2) variableOfTypeT1; // explicitne kompatibilný
Napríklad, int
je implicitne kompatibilný s dlho
a výslovne kompatibilný s krátky
:
int intVariable = 5; long longVariable = intVariable; // implicitne kompatibilny short shortVariable = (short) intVariable; // explicitne kompatibilný
Implicitná a explicitná kompatibilita existuje nielen v priradeniach, ale aj v odovzdávaní parametrov z volania metódy do definície metódy a späť. Spolu so vstupnými parametrami to znamená aj odovzdanie výsledku funkcie, ktorý by ste vykonali ako výstupný parameter.
Poznač si to boolean
nie je kompatibilný s iným typom, ani primitívny a referenčný typ nemôže byť kompatibilný.
Parametre metódy
Hovoríme, že metóda číta vstupné parametre a zapisuje výstupné parametre. Parametre primitívnych typov sú vždy vstupné parametre. Návratová hodnota funkcie je vždy výstupným parametrom. Parametre typov odkazov môžu byť obidva: ak metóda zmení referenciu (alebo primitívny parameter), zmena zostane v metóde (čo znamená, že po ukončení hovoru nie je viditeľná mimo metódy - toto sa označuje ako volať podľa hodnoty). Ak metóda zmení sprostredkovaný objekt, zmena zostáva po vrátení z metódy - nazýva sa to volať referenciou.
(Referenčný) podtyp je implicitne kompatibilný s jeho nadtypom a supertyp je výslovne kompatibilný s jeho podtypom. To znamená, že referenčné typy sú kompatibilné iba v rámci ich hierarchickej vetvy - smerom nahor implicitne a smerom nadol explicitne:
referenceOfSuperType = referenceOfSubType; // implicitný kompatibilný referenceOfSubType = (SubType) referenceOfSuperType; // explicitne kompatibilný
Kompilátor Java zvyčajne umožňuje implicitnú kompatibilitu priradenia iba ak neexistuje nebezpečenstvo straty informácií za behu medzi rôznymi typmi. (Upozorňujeme však, že toto pravidlo nie je platné pre stratu presnosti, napríklad pri zadaní z int
plávať.) Napríklad int
je implicitne kompatibilný s dlho
pretože a dlho
premenná drží každý int
hodnotu. Naproti tomu a krátky
premenná nedrží žiadnu int
hodnoty; medzi týmito prvkami je teda povolená iba výslovná kompatibilita.
Upozorňujeme, že implicitná kompatibilita na obrázku 6 predpokladá vzťah tranzitívny: krátky
je kompatibilný s dlho
.
Podobne ako na obrázku 6, je vždy možné priradiť odkaz na podtyp int
odkaz na supertyp. Majte na pamäti, že rovnaké zadanie v opačnom smere by mohlo spôsobiť a ClassCastException
, avšak kompilátor Java to umožňuje iba s castingom typu.
Kovariancia a kontrariance pre typy polí
V Jave sú niektoré typy polí kovariantné a / alebo kontrariantné. V prípade kovariancie to znamená, že ak T
je kompatibilný s U
potom T []
je tiež kompatibilný s U []
. V prípade rozporu to znamená U []
je kompatibilný s T []
. Polia primitívnych typov sú v Jave invariantné:
longArray = intArray; // chyba typu shortArray = (short []) intArray; // chyba typu
Polia referenčných typov sú implicitne kovariantný a výslovne proti, avšak:
SuperType [] superArray; SubType [] subArray; ... superArray = subArray; // implicitný kovariant subArray = (SubType []) superArray; // výslovný kontrariant
Andreas Solymosi Obrázok 7. Implicitná kovariancia pre polia
To prakticky znamená, že priradenie komponentov poľa môže spôsobiť ArrayStoreException
za behu. Ak je odkaz na pole z Supertyp
odkazuje na objekt poľa z Podtyp
, a jeden z jeho komponentov je potom priradený k a Supertyp
objekt, potom:
superArray [1] = nový SuperType (); // hodí ArrayStoreException
Toto sa niekedy nazýva kovariančný problém. Skutočným problémom nie je ani tak výnimka (ktorej by sa dalo vyhnúť pri disciplíne programovania), ale to, že virtuálny stroj musí za behu skontrolovať každé priradenie v prvku poľa. Toto vystavuje Javu nevýhode efektívnosti oproti jazykom bez kovariancie (kde je zakázané kompatibilné priradenie odkazov na pole) alebo jazykom ako Scala, kde je možné kovarianciu vypnúť.
Príklad pre kovarianciu
V jednoduchom príklade je odkaz na pole typu Objekt []
ale objekt poľa a prvky majú rôzne triedy:
Object [] objectArray; // referencia poľa objectArray = nový reťazec [3]; // objekt poľa; kompatibilné priradenie objectArray [0] = nové celé číslo (5); // hodí ArrayStoreException
Z dôvodu kovariancie kompilátor nemôže skontrolovať správnosť posledného priradenia k prvkom poľa - JVM to robí a za značné náklady. Kompilátor však môže náklady optimalizovať, ak sa medzi typmi polí nepoužíva kompatibilita typov.
Andreas SolymosiPamätajte, že v Jave je pre referenčnú premennú určitého typu zakázané odkazovať na objekt jej supertypu: šípky na obrázku 8 nesmú smerovať nahor.
Odchýlky a zástupné znaky vo všeobecných typoch
Všeobecné (parametrizované) typy sú implicitne nemenný v Jave, čo znamená, že rôzne inštancie generického typu nie sú navzájom kompatibilné. Kompatibilita nebude mať za následok ani obsadenie typu:
Generický superGenerický; Generické subGenerické; subGeneric = (Generic) superGeneric; // chyba typu superGeneric = (Generic) subGeneric; // chyba typu
Typové chyby vznikajú, aj keď subGeneric.getClass () == superGeneric.getClass ()
. Problém je v tom, že metóda getClass ()
určuje prvotný typ - to je dôvod, prečo parameter typu nepatrí k podpisu metódy. Teda dve deklarácie metód
metóda prázdnoty (generická p); metóda prázdnoty (generická p);
sa nesmú vyskytovať spoločne v definícii rozhrania (alebo abstraktnej triedy).