Pochopení kompatibility typů je zásadní pro psaní dobrých programů Java, ale souhra odchylek mezi prvky jazyka Java se pro nezasvěcené může zdát vysoce akademická. Tento článek je určen vývojářům softwaru, kteří jsou připraveni tuto výzvu zvládnout! Část 1 odhaluje kovariantní a kontrariantní vztahy mezi jednoduššími prvky, jako jsou typy polí a generické typy, a také speciální prvek jazyka Java, zástupný znak. Část 2 zkoumá závislost typu a rozptyl v běžných příkladech API a ve výrazech lambda.
stáhnout Stáhněte si zdroj Získejte zdrojový kód pro tento článek, „Závislost typu v Javě, část 1.“ Vytvořil pro JavaWorld Dr. Andreas Solymosi.Pojmy a terminologie
Než se dostaneme do vztahů kovariance a kontravariance mezi různými prvky jazyka Java, ujistěte se, že máme sdílený koncepční rámec.
Kompatibilita
V objektově orientovaném programování kompatibilita odkazuje na směrovaný vztah mezi typy, jak je znázorněno na obrázku 1.
Andreas Solymosi Říkáme, že jsou dva typy kompatibilní v Javě, pokud je možné přenášet data mezi proměnnými typů. Přenos dat je možný, pokud to kompilátor přijme, a provádí se prostřednictvím přiřazení nebo předávání parametrů. Jako příklad, krátký
je kompatibilní s int
protože úkol intVariable = shortVariable;
je možné. Ale booleovský
není kompatibilní s int
protože úkol intVariable = booleanVariable;
není možné; překladač to nepřijme.
Protože kompatibilita je někdy řízený vztah T1
je kompatibilní s T2
ale T2
není kompatibilní s T1
, nebo ne stejným způsobem. Uvidíme to dále, až budeme diskutovat o explicitní nebo implicitní kompatibilitě.
Důležité je, že je možná kompatibilita mezi referenčními typy pouze v hierarchii typů. Všechny typy tříd jsou kompatibilní s Objekt
například proto, že všechny třídy implicitně dědí z Objekt
. Celé číslo
není kompatibilní s Plovák
, nicméně, protože Plovák
není nadtřída Celé číslo
. Celé číslo
je kompatibilní s Číslo
, protože Číslo
je (abstraktní) nadtřída Celé číslo
. Protože jsou umístěny ve stejné hierarchii typů, kompilátor přijímá přiřazení numberReference = integerReference;
.
Mluvíme o implicitní nebo explicitní kompatibilita, v závislosti na tom, zda musí být kompatibilita explicitně označena nebo ne. Například short je implicitně kompatibilní s int
(jak je uvedeno výše), ale ne naopak: zadání shortVariable = intVariable;
není možné. Krátký je však výslovně kompatibilní s int
, protože zadání shortVariable = (short) intVariable;
je možné. Zde musíme označit kompatibilitu pomocí odlévání, známý také jako převod typu.
Podobně mezi typy odkazů: integerReference = numberReference;
je nepřijatelné, pouze integerReference = (Integer) numberReference;
bude přijat. Proto, Celé číslo
je implicitně kompatibilní s Číslo
ale Číslo
je pouze výslovně kompatibilní s Celé číslo
.
Závislost
Typ může záviset na jiných typech. Například typ pole int []
záleží na primitivním typu int
. Podobně obecný typ ArrayList
závisí na typu Zákazník
. Metody mohou být také závislé na typu, v závislosti na typech jejich parametrů. Například metoda přírůstek void (celé číslo i)
; záleží na typu Celé číslo
. Některé metody (například některé obecné typy) závisí na více než jednom typu - například metody, které mají více než jeden parametr.
Kovariance a kontravariance
Kovariance a kontravariance určují kompatibilitu na základě typů. V obou případech je rozptyl přímým vztahem. Kovariance lze přeložit jako „různé ve stejném směru“, nebo with-different, zatímco rozpor znamená „odlišný v opačném směru“ nebo proti-jiný. Kovariantní a kontravariantní typy nejsou stejné, ale existuje mezi nimi korelace. Názvy naznačují směr korelace.
Tak, kovariance znamená, že kompatibilita dvou typů implikuje kompatibilitu typů na nich závislých. Vzhledem k kompatibilitě typů se předpokládá, že závislé typy jsou kovariantní, jak je znázorněno na obrázku 2.
Andreas Solymosi Kompatibilita T1
na T2
znamená kompatibilitu NA1
) až NA2
). Závislý typ NA)
je nazýván kovariantní; nebo přesněji NA1
) je kovariantní k NA2
).
Pro další příklad: protože úkol numberArray = integerArray;
je možné (alespoň v Javě), typy polí Celé číslo[]
a Číslo[]
jsou kovariantní. Můžeme to tedy říci Celé číslo[]
je implicitně kovariantní na Číslo[]
. A zatímco opak není pravdou - zadání integerArray = numberArray;
není možné - zadání s odléváním typu (integerArray = (Integer []) numberArray;
) je možný; proto říkáme Číslo[]
je výslovně kovariantní na Celé číslo[]
.
Shrnout: Celé číslo
je implicitně kompatibilní s Číslo
, proto Celé číslo[]
je implicitně kovariantní k Číslo[]
, a Číslo[]
je výslovně kovariantní k Celé číslo[]
. Obrázek 3 ilustruje.
Obecně lze říci, že typy polí jsou v Javě kovariantní. Podíváme se na příklady kovariance mezi generickými typy dále v článku.
Rozporuplnost
Stejně jako kovariance je i kontravariance režie vztah. Zatímco kovariance znamená with-different, kontravariance znamená proti-jiný. Jak jsem již zmínil, jména vyjadřují směr korelace. Je také důležité si uvědomit, že rozptyl není atributem typů obecně, ale pouze závislý typy (například pole a generické typy a také metody, kterým se budu věnovat v části 2).
Závislý typ, jako je NA)
je nazýván protikladný pokud je kompatibilita T1
na T2
znamená kompatibilitu NA2
) až NA1
). Obrázek 4 ilustruje.
Prvek jazyka (typ nebo metoda) NA)
záleží na T
je kovariantní pokud je kompatibilita T1
na T2
znamená kompatibilitu NA1
) až NA2
). Pokud je kompatibilita T1
na T2
znamená kompatibilitu NA2
) až NA1
), pak typ NA)
je protikladný. Pokud je kompatibilita T1
mezi T2
neznamená žádnou kompatibilitu mezi NA1
) a NA2
), pak NA)
je neměnný.
Typy polí v Javě nejsou implicitně v rozporu, ale mohou být výslovně v rozporu , stejně jako obecné typy. Několik příkladů uvedu dále v článku.
Prvky závislé na typu: Metody a typy
V Javě jsou metody, typy polí a obecné (parametrizované) typy prvky závislé na typu. Metody jsou závislé na typech jejich parametrů. Typ pole, T []
, je závislá na typech jeho prvků, T
. Obecný typ G
je závislá na jeho parametru typu, T
. Obrázek 5 ilustruje.
Většinou se tento článek zaměřuje na kompatibilitu typů, ačkoli se na konci 2. části dotknu kompatibility mezi metodami.
Implicitní a explicitní kompatibilita typů
Dříve jste viděli typ T1
bytost implicitně (nebo výslovně) kompatibilní s T2
. To platí pouze v případě, že je přiřazení proměnné typu T1
na proměnnou typu T2
je povoleno bez (nebo s) značením. Cast cast je nejčastější způsob, jak označit explicitní kompatibilitu:
variableOfTypeT2 = variableOfTypeT1; // implicitní kompatibilní variableOfTypeT2 = (T2) variableOfTypeT1; // explicitní kompatibilní
Například, int
je implicitně kompatibilní s dlouho
a výslovně kompatibilní s krátký
:
int intVariable = 5; long longVariable = intVariable; // implicitní kompatibilní short shortVariable = (short) intVariable; // explicitní kompatibilní
Implicitní a explicitní kompatibilita existuje nejen v přiřazeních, ale také v předávání parametrů z volání metody do definice metody a zpět. Spolu se vstupními parametry to znamená také předání výsledku funkce, který byste udělali jako výstupní parametr.
Všimněte si, že booleovský
není kompatibilní s žádným jiným typem, ani primitivní a referenční typ nemůže být nikdy kompatibilní.
Parametry metody
Říkáme, že metoda čte vstupní parametry a zapisuje výstupní parametry. Parametry primitivních typů jsou vždy vstupní parametry. Návratová hodnota funkce je vždy výstupním parametrem. Parametry typů odkazů mohou být oba: pokud metoda změní odkaz (nebo primitivní parametr), změna zůstane v metodě (to znamená, že po volání není viditelná mimo metodu - to se nazývá volání podle hodnoty). Pokud metoda změní odkazovaný objekt, změna zůstane po vrácení z metody - to se nazývá volejte odkazem.
Podtyp (reference) je implicitně kompatibilní se svým nadtypem a supertyp je výslovně kompatibilní se svým podtypem. To znamená, že referenční typy jsou kompatibilní pouze v rámci jejich hierarchické větve - nahoru implicitně a dolů explicitně:
referenceOfSuperType = referenceOfSubType; // implicitní kompatibilní referenceOfSubType = (SubType) referenceOfSuperType; // explicitní kompatibilní
Kompilátor Java obvykle umožňuje implicitní kompatibilitu pro přiřazení pouze pokud neexistuje nebezpečí ztráty informací za běhu mezi různými typy. (Všimněte si však, že toto pravidlo není platné pro ztrátu přesnosti, například při přiřazení z int
plavat.) Například int
je implicitně kompatibilní s dlouho
protože a dlouho
proměnná obsahuje všechny int
hodnota. Naproti tomu a krátký
proměnná nedrží žádnou int
hodnoty; mezi těmito prvky je tedy povolena pouze explicitní kompatibilita.
Všimněte si, že implicitní kompatibilita na obrázku 6 předpokládá vztah tranzitivní: krátký
je kompatibilní s dlouho
.
Podobně jako to, co vidíte na obrázku 6, je vždy možné přiřadit odkaz na podtyp int
odkaz na supertyp. Pamatujte, že stejný úkol v opačném směru by mohl hodit a ClassCastException
, avšak kompilátor Java to umožňuje pouze s castingem typu.
Kovariance a kontravariance pro typy polí
V Javě jsou některé typy polí kovariantní a / nebo kontrariantní. V případě kovariance to znamená, že pokud T
je kompatibilní s U
, pak T []
je také kompatibilní s U []
. V případě rozporu to znamená U []
je kompatibilní s T []
. Pole primitivních typů jsou v Javě neměnná:
longArray = intArray; // chyba typu shortArray = (short []) intArray; // chyba typu
Pole referenčních typů jsou implicitně kovariantní a výslovně v rozporu, nicméně:
SuperType [] superArray; SubType [] subArray; ... superArray = subArray; // implicitní kovariant subArray = (SubType []) superArray; // explicitní kontravariant
Andreas Solymosi Obrázek 7. Implicitní kovariance pro pole
To prakticky znamená, že by mohlo přiřadit přiřazení komponent pole ArrayStoreException
za běhu. Pokud je odkaz na pole Supertyp
odkazuje na objekt pole Podtyp
, a jedna z jeho složek je poté přiřazena a Supertyp
objekt, pak:
superArray [1] = nový SuperType (); // vyvolá ArrayStoreException
Toto se někdy nazývá kovarianční problém. Skutečným problémem není ani tak výjimka (které by bylo možné se vyhnout programovací disciplínou), ale to, že virtuální stroj musí za běhu zkontrolovat každé přiřazení v prvku pole. To staví Javu do nevýhody účinnosti oproti jazykům bez kovariance (kde je zakázáno kompatibilní přiřazení odkazů na pole) nebo jazykům jako Scala, kde lze kovariance vypnout.
Příklad pro kovarianci
V jednoduchém příkladu je odkaz na pole typu Objekt[]
ale objekt pole a prvky jsou různých tříd:
Object [] objectArray; // odkaz na pole objectArray = nový řetězec [3]; // objekt pole; kompatibilní přiřazení objectArray [0] = nové celé číslo (5); // vyvolá ArrayStoreException
Z důvodu kovariance kompilátor nemůže zkontrolovat správnost posledního přiřazení prvkům pole - JVM to dělá a za značné náklady. Kompilátor však může optimalizovat výdaje, pokud neexistuje použití kompatibility typů mezi typy polí.
Andreas SolymosiPamatujte, že v Javě je pro referenční proměnnou nějakého typu zakázáno odkazovat na objekt jejího supertypu: šipky na obrázku 8 nesmí směřovat nahoru.
Odchylky a zástupné znaky v obecných typech
Obecné (parametrizované) typy jsou implicitně neměnný v Javě, což znamená, že různé instance obecného typu nejsou navzájem kompatibilní. Dokonce ani casting nebude mít za následek kompatibilitu:
Generická superGenerická; Generické subGenerické; subGeneric = (Generic) superGeneric; // chyba typu superGeneric = (obecná) subGeneric; // chyba typu
Přesto se vyskytují chyby typu subGeneric.getClass () == superGeneric.getClass ()
. Problém je v tom, že metoda getClass ()
určuje surový typ - to je důvod, proč parametr typu nepatří k podpisu metody. Tedy dvě deklarace metody
metoda prázdnoty (obecná p); metoda prázdnoty (obecná p);
nesmí se vyskytovat společně v definici rozhraní (nebo abstraktní třídy).