Programování

Závislost typu v Javě, část 1

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 Objektnapří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é čísloje 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.

Andreas Solymosi

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.

Andreas Solymosi

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.

Andreas Solymosi

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.

Andreas Solymosi

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 Solymosi

Pamatujte, ž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).