Programování

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

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í

?rozšiřuje

OK Chyba OK Obsazení Obsazení Obsazení

Kontrastní

? super

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.

$config[zx-auto] not found$config[zx-overlay] not found