Programovanie

Typ závislosti v Jave, časť 2

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 dvojdielny článok je určený pre vývojárov softvéru, ktorí sú pripravení zvládnuť túto výzvu! 1. časť odhalila 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. V časti 2 sa skúma závislosť typu v rozhraní Java Collections API, v generikách a vo výrazoch lambda.

Skočíme priamo dovnútra, takže ak ste ešte nečítali 1. časť, odporúčam začať tam.

Príklady rozhrania API

Za náš prvý príklad zvážte Komparátor verzia java.util.Collections.sort (), z rozhrania Java Collections API. Podpis tejto metódy je:

  void sort (zoznam, komparátor c) 

The sort () metóda triedi ľubovoľné Zoznam. Väčšinou je jednoduchšie použiť preťaženú verziu s podpisom:

 triediť (Zoznam) 

V tomto prípade, rozširuje Porovnateľné vyjadruje, že sort () možno volať, iba ak sú potrebné prvky na porovnávanie metód (menovite porovnať s) boli definované v type prvku (alebo v jeho supertype, vďaka ? Super T):

 sort (integerList); // Celé číslo implementuje porovnateľné triedenie (customerList); // funguje, iba ak zákazník implementuje porovnateľné 

Používanie generík na porovnanie

Je zrejmé, že zoznam je rozdeliteľný, iba ak je možné navzájom porovnávať jeho prvky. Porovnanie sa vykonáva jedinou metódou porovnať s, ktorá patrí k rozhraniu Porovnateľné. Musíte implementovať porovnať s v triede prvkov.

Tento typ prvku je však možné triediť iba jedným spôsobom. Môžete napríklad triediť a Zákazník podľa ich ID, ale nie podľa dátumu narodenia alebo PSČ. Pomocou Komparátor verzia sort () je flexibilnejší:

 publicstatic void sort (zoznam, komparátor c) 

Teraz porovnávame prvky nie v triede prvkov, ale v dodatku Komparátor objekt. Toto všeobecné rozhranie má jednu metódu objektu:

 int porovnanie (To, T02); 

Kontrastné parametre

Vytvorenie inštancie objektu viackrát umožňuje triediť objekty podľa rôznych kritérií. Ale skutočne potrebujeme taký komplikovaný Komparátor parameter typu? Väčšinou, Komparátor stačilo by. Mohli by sme použiť jeho porovnať () metóda na porovnanie akýchkoľvek dvoch prvkov v Zoznam takto:

trieda DateComparator implementuje komparátor {public int compare (Date d1, Date d2) {return ...} // porovnáva dva objekty Date} List dateList = ...; // Zoznam triedených objektov dátumu (dateList, new DateComparator ()); // triedi dateList 

Použitie komplikovanejšej verzie metódy Collection.sort () nastaviť nás na ďalšie prípady použitia. Parameter kontravariantného typu Porovnateľné umožňuje triediť zoznam typov Zoznam, pretože java.util.Date je supertyp java.sql.Date:

 Zoznam sqlList = ...; sort (sqlList, new DateComparator ()); 

Ak vynecháme rozpor v sort () podpis (iba s použitím alebo bližšie neurčené, nebezpečné ), potom kompilátor odmietne posledný riadok ako chybu typu.

Aby bolo možné zavolať

 sort (sqlList, new SqlDateComparator ()); 

museli by ste napísať extra nevýraznú triedu:

 trieda SqlDateComparator rozširuje DateComparator {} 

Ďalšie metódy

Zbierka.sort () nie je jedinou metódou rozhrania Java Collections API vybavenou kontravariantným parametrom. Metódy ako pridať všetko(), binarySearch (), copy (), vyplniť (), a tak ďalej, možno použiť s podobnou flexibilitou.

Zbierky metódy ako max () a min () ponúkajú protikladné typy výsledkov:

 verejná statická  T max (zbierka zbierky) {...} 

Ako tu vidíte, parameter typu je možné požadovať, aby vyhovoval viac ako jednej podmienke, a to iba pomocou &. The rozširuje Object sa môže javiť ako nadbytočný, ale stanovuje to max () vráti výsledok typu Objekt a nie riadok Porovnateľné v bytecode. (V bytecode nie sú zadané žiadne parametre typu.)

Preťažená verzia max () s Komparátor je ešte zábavnejšie:

 verejná statická T max (zbierka zbierky, porovnávacia kompilácia) 

Toto max () má obidva protikladné a parametre kovariančného typu. Zatiaľ čo prvky Zbierka musia byť (možno odlišných) podtypov určitého (nie výslovne uvedeného) typu, Komparátor musí byť inštancovaný pre supertyp rovnakého typu. Veľa sa vyžaduje od inferenčného algoritmu kompilátora, aby sa tento intermediárny typ odlíšil od volania, ako je tento:

 Zbierka zbierka = ...; Komparátor komparátor = ...; max (zber, komparátor); 

Škatuľková väzba parametrov typu

Ako posledný príklad závislosti a odchýlky od typu v rozhraní Java Collections API poďme prehodnotiť podpis súboru sort () s Porovnateľné. Používa sa oboje predlžuje a Super, ktoré sú v krabici:

 statický  void sort (Zoznam zoznamu) {...} 

V tomto prípade nás nezaujíma tak kompatibilita referencií, ako skôr záväznosť inštancie. Táto inštancia sort () metóda triedi a zoznam objekt s prvkami implementujúcimi triedu Porovnateľné. Vo väčšine prípadov by triedenie fungovalo aj bez v podpise metódy:

 triediť (dateList); // java.util.Date implementuje Comparable sort (sqlList); // java.sql.Date implementuje Srovnateľné 

Dolná hranica parametra typu však umožňuje ďalšiu flexibilitu. Porovnateľné nemusí byť nevyhnutne implementované v triede prvkov; stačí to implementovať do nadtriedy. Napríklad:

 trieda SuperClass implementuje Porovnateľné {public int compareTo (SuperClass s) {...}} trieda Podtrieda rozširuje SuperClass {} // bez preťaženia compareTo () Zoznam superList = ...; sort (superList); Zoznam subList = ...; triediť (subList); 

Kompilátor prijíma posledný riadok s

 statický  void sort (Zoznam zoznamu) {...} 

a odmieta to s

statický  void sort (Zoznam zoznamu) {...} 

Dôvodom tohto odmietnutia je ten typ Podtrieda (ktoré by kompilátor určil z typu Zoznam v parametri podzoznam) nie je vhodný ako typový parameter pre T sa rozširuje na porovnateľné. Typ Podtrieda nerealizuje Porovnateľné; iba realizuje Porovnateľné. Tieto dva prvky však nie sú kompatibilné z dôvodu neexistencie implicitnej kovariancie Podtrieda je kompatibilný s Supertrieda.

Na druhej strane, ak použijeme , kompilátor neočakáva Podtrieda vykonávať Porovnateľné; stačí ak Supertrieda robí to. Je to dosť, pretože metóda porovnať s() sa dedí z Supertrieda a je možné o ne požiadať Podtrieda objekty: vyjadruje to s účinkom v rozpore.

Kontrastný prístup k premenným parametra typu

Horná alebo dolná hranica sa vzťahuje iba na parameter typu inštancií sprostredkovaných kovariantným alebo kontravariantným odkazom. V prípade Generická kovariantná referencia; a Generic contravariantReference;, môžeme vytvárať a odkazovať na rôzne objekty Generické inštancie.

Pre parameter a typ výsledku metódy (napríklad pre metódu) platia rôzne pravidlá vstup a výkon typy parametrov všeobecného typu). Ľubovoľný objekt kompatibilný s Podtyp možno odovzdať ako parameter metódy napíš (), ako je definované vyššie.

 contravariantReference.write (nový SubType ()); // OK contravariantReference.write (new SubSubType ()); // OK too contravariantReference.write (new SuperType ()); // chyba typu ((Generic) contravariantReference) .write (new SuperType ()); // OK 

Z dôvodu kontrarariance je možné odovzdať parameter do napíš (). Toto je na rozdiel od kovariančného (tiež neobmedzeného) zástupného typu.

Situácia sa pre typ výsledku nezmení väzbou: čítať() stále prináša výsledok typu ?, kompatibilný iba s Objekt:

 Objekt o = contravariantReference.read (); SubType st = contravariantReference.read (); // chyba typu 

Posledný riadok produkuje chybu, aj keď sme deklarovali a kontravariantná referencia typu Generické.

Výsledný typ je kompatibilný s iným typom len potom typ referencie bol výslovne prevedený:

 SuperSuperType sst = ((všeobecné) contravariantReference) .read (); sst = (SuperSuperType) contravariantReference.read (); // bezpečnejšia alternatíva 

Príklady v predchádzajúcich zoznamoch ukazujú, že prístup na čítanie alebo zápis do premennej typu parameter správa sa rovnako, bez ohľadu na to, či sa to deje cez metódu (čítanie a zápis) alebo priamo (údaje v príkladoch).

Čítanie a zápis do premenných parametra typu

Tabuľka 1 ukazuje, že čítanie do Objekt premenná je vždy možná, pretože každá trieda a zástupný znak sú kompatibilné s Objekt. Písanie Objekt je možné len nad odporujúcim odkazom po príslušnom obsadení, pretože Objekt nie je kompatibilný so zástupnými znakmi. Čítanie bez vhadzovania do nevhodnej premennej je možné pomocou kovariančnej referencie. Písanie je možné s odporujúcim odkazom.

Tabuľka 1. Prístup na čítanie a zápis do premenných parametra typu

čítanie

(vstup)

čítať

Objekt

napíš

Objekt

čítať

supertyp

napíš

supertyp

čítať

podtyp

napíš

podtyp

Divoká karta

?

Ok Chyba Obsadenie Obsadenie Obsadenie Obsadenie

Kovariantný

? rozširuje sa

Ok Chyba Ok Obsadenie Obsadenie Obsadenie

Protikladný

?Super

Ok Obsadenie Obsadenie Obsadenie Obsadenie Ok

Riadky v tabuľke 1 odkazujú na druh odkazua stĺpce k typ údajov mať prístup. Nadpisy „supertypu“ a „podtypu“ označujú hranice zástupných znakov. Záznam „obsadenie“ znamená, že je potrebné odovzdať referenciu. Inštancia „OK“ v posledných štyroch stĺpcoch odkazuje na typické prípady pre kovarianciu a kontravarenciu.

Na konci tohto článku nájdete systematický testovací program pre tabuľku s podrobným vysvetlením.

Vytváranie objektov

Na jednej strane nemôžete vytvárať objekty zástupného typu, pretože sú abstraktné. Na druhej strane môžete vytvárať objekty poľa iba neobmedzeného zástupného typu. Nemôžete však vytvárať objekty iných všeobecných inštancií.

 Generic [] genericArray = new Generic [20]; // chyba typu Generic [] wildcardArray = new Generic [20]; // OK genericArray = (Generic []) wildcardArray; // nezaškrtnutá konverzia genericArray [0] = new Generic (); genericArray [0] = nový Generic (); // chyba typu wildcardArray [0] = new Generic (); // OK 

Kvôli kovariancii polí typ zástupného poľa Všeobecné [] je supertyp typu poľa všetkých inštancií; preto je možné priradenie v poslednom riadku vyššie uvedeného kódu.

V rámci generickej triedy nemôžeme vytvárať objekty parametra typu. Napríklad v konštruktore súboru ArrayList implementácia, objekt poľa musí byť typu Objekt [] pri stvorení. Potom ho môžeme previesť na typ poľa parametra type:

 trieda MyArrayList implementuje zoznam {private final E [] obsah; MyArrayList (int size) {content = new E [size]; // napíšte error content = (E []) nový objekt [veľkosť]; // riešenie} ...} 

Pre bezpečnejšie riešenie odovzdajte Trieda hodnota parametra skutočného typu pre konštruktor:

 obsah = (E []) java.lang.reflect.Array.novýInštancia(myClass, veľkosť); 

Viac parametrov typu

Všeobecný typ môže mať viac ako jeden parameter typu. Parametre typu nemenia správanie kovariancie a kontravarencie a môže sa vyskytnúť viac parametrov typu spoločne, ako je uvedené nižšie:

 trieda G {} referencia G; referencia = nový G (); // bez referencie odchýlky = new G (); // so spolurozhodovaním 

Všeobecné rozhranie java.util.Map sa často používa ako príklad pre parametre viacerých typov. Rozhranie má dva parametre typu, jeden pre kľúč a jeden pre hodnotu. Je užitočné priradiť objekty ku kľúčom, napríklad aby sme ich mohli ľahšie nájsť. Telefónny zoznam je príkladom a Mapa objekt pomocou viacerých parametrov typu: kľúčom je meno účastníka, hodnotou je telefónne číslo.

Implementácia rozhrania java.util.HashMap má konštruktor na prevod ľubovoľného Mapa objekt do asociačnej tabuľky:

 verejná mapa HashMap (mapa m) ... 

Z dôvodu kovariancie nemusí parameter typu objektu parametra v tomto prípade zodpovedať presným triedam parametrov typu K a V.. Namiesto toho sa dá prispôsobiť pomocou kovariancie:

 Mapujte zákazníkov; ... kontakty = nový HashMap (zákazníci); // kovariant 

Tu, Id je supertyp Zákaznícke čísloa Osoba je supertyp Zákazník.

Rozptyl metód

Hovorili sme o variancii typov; poďme teraz k trochu ľahšej téme.

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