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 dvoudílný článek je určen vývojářům softwaru, kteří jsou připraveni tuto výzvu zvládnout! Část 1 odhalila 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 v rozhraní Java Collections API, v generikách a ve výrazech lambda.
Skočíme přímo dovnitř, takže pokud jste ještě nečetli 1. část, doporučuji začít tam.
Příklady API pro kontrarariance
Pro náš první příklad zvažte Komparátor
verze java.util.Collections.sort ()
, z rozhraní API Java Collections. Podpis této metody je:
void sort (seznam, komparátor c)
The sort ()
metoda seřadí jakékoli Seznam
. Obvykle je jednodušší použít přetíženou verzi s podpisem:
třídit (seznam)
V tomto případě, rozšiřuje srovnatelné
vyjadřuje, že sort ()
lze volat pouze v případě, že jsou k dispozici prvky potřebné k porovnání metod (jmenovitě porovnat s)
byly definovány v typu prvku (nebo v jeho supertypu, díky ? super T)
:
sort (integerList); // Celé číslo implementuje srovnatelné řazení (customerList); // funguje pouze v případě, že zákazník implementuje srovnatelné
Použití generik pro srovnání
Je zřejmé, že seznam lze řadit, pouze pokud lze jeho prvky navzájem srovnávat. Porovnání se provádí jedinou metodou porovnat s
, který patří k rozhraní Srovnatelný
. Musíte implementovat porovnat s
ve třídě prvků.
Tento typ prvku lze však řadit pouze jedním způsobem. Můžete například třídit a Zákazník
podle jejich ID, ale ne podle narozenin nebo PSČ. Za použití Komparátor
verze sort ()
je flexibilnější:
publicstatic void sort (seznam, srovnávač c)
Nyní porovnáváme prvky, které nejsou ve třídě prvků, ale v další Komparátor
objekt. Toto obecné rozhraní má jednu metodu objektu:
int srovnání (T01, T02);
Kontravariantní parametry
Vytvoření instance objektu vícekrát umožňuje třídit objekty podle různých kritérií. Ale opravdu potřebujeme takový komplikovaný Komparátor
parametr typu? Většinou, Komparátor
stačilo by. Mohli bychom použít jeho porovnat ()
metoda pro porovnání jakýchkoli dvou prvků v souboru Seznam
objekt takto:
třída DateComparator implementuje komparátor {public int compare (Date d1, Date d2) {return ...} // porovnává dva objekty Date} List dateList = ...; // Seznam datových objektů sort (dateList, new DateComparator ()); // třídí dateList
Použití složitější verze metody Collection.sort ()
nastavit nás pro další případy použití. Parametr kontravariantního typu Srovnatelný
umožňuje třídit seznam typů Seznam
, protože java.util.Date
je supertyp java.sql.Date
:
Seznam sqlList = ...; sort (sqlList, new DateComparator ());
Pokud vynecháme rozpor v sort ()
podpis (pouze pomocí nebo nespecifikované, nebezpečné
), pak kompilátor odmítne poslední řádek jako chybu typu.
Aby bylo možné zavolat
sort (sqlList, new SqlDateComparator ());
museli byste napsat extra nevýraznou třídu:
třída SqlDateComparator rozšiřuje DateComparator {}
Další metody
Collections.sort ()
není jedinou metodou rozhraní Java Collections API vybavenou kontravariantním parametrem. Metody jako přidat vše()
, binarySearch ()
, kopírovat()
, vyplnit()
, a tak dále, lze použít s podobnou flexibilitou.
Sbírky
metody jako max ()
a min ()
nabízet protikladné typy výsledků:
veřejná statika T max (kolekce Collection) {...}
Jak vidíte zde, parametr typu lze požádat, aby vyhověl více než jedné podmínce, pouhým použitím &
. The rozšiřuje Object
se může zdát nadbytečné, ale stanoví to max ()
vrací výsledek typu Objekt
a ne z řady Srovnatelný
v bytecode. (V bytecode nejsou žádné parametry typu.)
Přetížená verze max ()
s Komparátor
je ještě zábavnější:
public static T max (Collection collection, Comparator comp)
Tento max ()
má oba kontravariantní a parametry kovariantního typu. Zatímco prvky Sbírka
musí být (možná odlišných) podtypů určitého (ne explicitně daného) typu, Komparátor
musí být vytvořena instance pro supertyp stejného typu. K odlišení algoritmu odvození kompilátoru je zapotřebí mnoho, aby se tento mezilehlý typ odlišil od volání, jako je tento:
Sbírka sbírka = ...; Komparátor komparátor = ...; max (sběr, komparátor);
Krabicová vazba parametrů typu
Jako náš poslední příklad závislosti na typu a odchylky v rozhraní Java Collections API pojďme znovu zvážit podpis sort ()
s Srovnatelný
. Všimněte si, že používá obojí rozšiřuje
a super
, které jsou v rámečku:
statický void sort (List List) {...}
V tomto případě nás nezajímá tak kompatibilita referencí, jako jsme ve vazbě instance. Tato instance sort ()
metoda seřadí a seznam
objekt s prvky třídy implementující Srovnatelný
. Ve většině případů by třídění fungovalo bez v podpisu metody:
sort (dateList); // java.util.Date implementuje srovnatelné řazení (sqlList); // java.sql.Date implementuje Srovnatelné
Dolní mez parametru typu však umožňuje další flexibilitu. Srovnatelný
nemusí být nutně implementováno ve třídě prvků; stačí ji implementovat do nadtřídy. Například:
třída SuperClass implementuje Srovnatelný {public int compareTo (SuperClass s) {...}} třída Podtřída rozšiřuje SuperClass {} // bez přetížení z CompareTo () Seznam superList = ...; sort (superList); Seznam subList = ...; sort (subList);
Kompilátor přijímá poslední řádek s
statický void sort (List List) {...}
a odmítá to s
statický void sort (List List) {...}
Důvodem tohoto odmítnutí je ten typ Podtřída
(který kompilátor určí z typu Seznam
v parametru subList
) není vhodný jako typový parametr pro T rozšiřuje srovnatelné
. Typ Podtřída
neprovádí Srovnatelný
; pouze implementuje Srovnatelný
. Tyto dva prvky však nejsou kompatibilní kvůli nedostatku implicitní kovariance Podtřída
je kompatibilní s Super třída
.
Na druhou stranu, pokud použijeme , překladač neočekává
Podtřída
provádět Srovnatelný
; stačí, když Super třída
to dělá. Je to dost, protože metoda porovnat s()
je zděděno z Super třída
a lze je vyvolat Podtřída
objekty: vyjadřuje to, čímž působí protiklad.
Kontrastní přístup k proměnným parametru typu
Horní nebo dolní mez platí pouze pro parametr typu instancí odkazovaných kovariantním nebo kontravariantním odkazem. V případě Obecná kovariantní reference;
a Obecná kontravariantní reference;
, můžeme vytvářet a odkazovat objekty různých Obecný
instance.
Pro parametr a typ výsledku metody (například pro.) Platí různá pravidla vstup a výstup typy parametrů obecného typu). Libovolný objekt kompatibilní s Podtyp
lze předat jako parametr metody napsat()
, jak je definováno výše.
contravariantReference.write (nový SubType ()); // OK contravariantReference.write (new SubSubType ()); // OK příliš contravariantReference.write (nový SuperType ()); // type error ((Generic) contravariantReference) .write (new SuperType ()); // OK
Z důvodu kontrarariance je možné předat parametr napsat()
. To je na rozdíl od kovariantního (také neomezeného) typu zástupného znaku.
Situace se pro typ výsledku nezmění vazbou: číst()
stále přináší výsledek typu ?
, kompatibilní pouze s Objekt
:
Objekt o = contravariantReference.read (); SubType st = contravariantReference.read (); // chyba typu
Poslední řádek produkuje chybu, i když jsme deklarovali a kontravariantReference
typu Obecný
.
Typ výsledku je kompatibilní s jiným typem pouze potom typ odkazu byl výslovně převeden:
SuperSuperType sst = ((obecný) contravariantReference) .read (); sst = (SuperSuperType) contravariantReference.read (); // bezpečnější alternativa
Příklady v předchozích výpisy ukazují, že čtení nebo zápis přístup k proměnné typu parametr
chová se stejným způsobem, bez ohledu na to, zda se to děje nad metodou (čtení a zápis) nebo přímo (data v příkladech).
Čtení a zápis do proměnných parametru typu
Tabulka 1 ukazuje, že čtení do souboru Objekt
proměnná je vždy možná, protože každá třída a zástupný znak jsou kompatibilní Objekt
. Psaní Objekt
je možné pouze přes kontravariantní odkaz po příslušném obsazení, protože Objekt
není kompatibilní se zástupnými znaky. Čtení bez přetypování do nevhodné proměnné je možné s kovariantním odkazem. Psaní je možné s kontravariantním odkazem.
Tabulka 1. Přístup ke čtení a zápisu do proměnných parametru typu
čtení (vstup) | číst Objekt | napsat Objekt | číst supertyp | napsat supertyp | číst podtyp | napsat podtyp |
Divoká karta
| OK | Chyba | Obsazení | Obsazení | Obsazení | Obsazení |
Kovariantní
| OK | Chyba | OK | Obsazení | Obsazení | Obsazení |
Kontrastní
| OK | Obsazení | Obsazení | Obsazení | Obsazení | OK |
Řádky v tabulce 1 odkazují na druh odkazua sloupce do typ dat pro přístup. Nadpisy „supertypu“ a „podtypu“ označují hranice zástupných znaků. Položka „cast“ znamená, že reference musí být obsazena. Instance „OK“ v posledních čtyřech sloupcích odkazuje na typické případy pro kovarianci a kontrarariance.
Na konci tohoto článku najdete systematický testovací program pro tabulku s podrobným vysvětlením.
Vytváření objektů
Na jedné straně nemůžete vytvářet objekty typu zástupných znaků, protože jsou abstraktní. Na druhou stranu můžete vytvářet objekty pole pouze neomezeného zástupného typu. Nelze však vytvářet objekty jiných obecných instancí.
Generic [] genericArray = new Generic [20]; // chyba typu Generic [] wildcardArray = new Generic [20]; // OK genericArray = (Generic []) wildcardArray; // nezaškrtnutá konverze genericArray [0] = new Generic (); genericArray [0] = nový Generic (); // chyba typu wildcardArray [0] = new Generic (); // OK
Kvůli kovarianci polí, typu zástupného pole Obecný[]
je supertyp typu pole všech instancí; proto je možné přiřazení v posledním řádku výše uvedeného kódu.
V rámci obecné třídy nemůžeme vytvářet objekty parametru typu. Například v konstruktoru an ArrayList
implementace, objekt pole musí být typu Objekt[]
po stvoření. Poté jej můžeme převést na typ pole parametru type:
třída MyArrayList implementuje seznam {private final E [] obsah; MyArrayList (int size) {content = new E [size]; // zadejte error content = (E []) nový objekt [velikost]; // řešení} ...}
Pro bezpečnější řešení předejte Třída
hodnota parametru skutečného typu pro konstruktor:
content = (E []) java.lang.reflect.Array.novýInstance(myClass, velikost);
Více parametrů typu
Obecný typ může mít více než jeden parametr typu. Parametry typu nemění chování kovariance a kontravariance a může se vyskytnout více parametrů typu společně, jak je znázorněno níže:
třída G {} G reference; reference = nový G (); // bez reference odchylky = new G (); // s ko- a kontravariance
Obecné rozhraní java.util.Map
se často používá jako příklad pro více parametrů typu. Rozhraní má dva parametry typu, jeden pro klíč a druhý pro hodnotu. Je užitečné přiřadit objekty ke klíčům, například abychom je mohli snáze najít. Příkladem telefonního seznamu je telefonní seznam Mapa
objekt využívající více parametrů typu: klíčem je jméno účastníka, hodnotou je telefonní číslo.
Implementace rozhraní java.util.HashMap
má konstruktor pro převod libovolného Mapa
objekt do asociační tabulky:
veřejná HashMap (mapa m) ...
Z důvodu kovariance nemusí parametr typu objektu parametru v tomto případě odpovídat přesným třídám parametrů typu K.
a PROTI
. Místo toho jej lze upravit pomocí kovariance:
Mapujte zákazníky; ... kontakty = nový HashMap (zákazníci); // kovariant
Tady, Id
je supertyp Zákaznické číslo
, a Osoba
je supertyp Zákazník
.
Rozptyl metod
Mluvili jsme o rozptylu typů; Nyní se podívejme na poněkud jednodušší téma.