Základy generických jazykov Java

1. Úvod

Java Generics boli predstavené v JDK 5.0 s cieľom znížiť chyby a pridať ďalšiu vrstvu abstrakcie k typom.

Tento článok je rýchlym úvodom do témy Generics in Java, cieľa, ktorý stojí za nimi, a toho, ako je možné ich použiť na zlepšenie kvality nášho kódu.

2. Potreba generík

Poďme si predstaviť scenár, keď chceme vytvoriť zoznam v Jave na uloženie Celé číslo; môžeme byť v pokušení napísať:

Zoznam zoznam = nový LinkedList (); list.add (nové celé číslo (1)); Celé číslo i = list.iterator (). Next (); 

Prekvapivo sa kompilátor bude sťažovať na posledný riadok. Nevie, aký dátový typ sa vráti. Kompilátor bude vyžadovať explicitné obsadenie:

Celé číslo i = (Celé číslo) list.iterator.next ();

Neexistuje žiadna zmluva, ktorá by mohla zaručiť, že návratový typ zoznamu je Celé číslo. Definovaný zoznam by mohol obsahovať akýkoľvek objekt. Vieme iba to, že zoznam získavame kontrolou kontextu. Pri pohľade na typy môže iba zaručiť, že ide o Objekt, preto vyžaduje výslovné obsadenie, aby sa zaistilo, že typ je bezpečný.

Toto obsadenie môže byť nepríjemné, vieme, že dátový typ v tomto zozname je Celé číslo. Obsadenie tiež zahlcuje náš kód. Môže spôsobiť chyby za behu súvisiace s typom, ak programátor urobí chybu s explicitným odovzdaním.

Bolo by oveľa jednoduchšie, keby programátori mohli vyjadriť svoj úmysel používať konkrétne typy a prekladač môže zabezpečiť správnosť tohto typu. Toto je základná myšlienka generických liekov.

Upravme prvý riadok predchádzajúceho úryvku kódu na:

Zoznam zoznam = nový LinkedList ();

Pridaním diamantového operátora obsahujúceho typ zúžime špecializáciu tohto zoznamu iba na Celé číslo typ, tj. určíme typ, ktorý sa bude nachádzať v zozname. Kompilátor môže vynútiť typ v čase kompilácie.

V malých programoch by sa to mohlo zdať ako triviálny doplnok, vo väčších programoch to však môže pridať značnú robustnosť a uľahčiť čítanie programu.

3. Generické metódy

Všeobecné metódy sú tie metódy, ktoré sú písané s jedinou deklaráciou metódy a dajú sa nazvať argumentmi rôznych typov. Kompilátor zabezpečí správnosť použitého typu. Tu sú niektoré vlastnosti všeobecných metód:

  • Všeobecné metódy majú pred návratovým typom deklarácie metódy parameter typu (operátor diamantu, ktorý typ obklopuje)
  • Parametre typu môžu byť ohraničené (hranice sú vysvetlené ďalej v článku)
  • Generické metódy môžu mať v podpise metódy oddelené čiarkami rôzne parametre typu
  • Telo metódy pre všeobecnú metódu je ako bežná metóda

Príklad definovania všeobecnej metódy na prevod poľa na zoznam:

public List fromArrayToList (T [] a) {return Arrays.stream (a) .collect (Collectors.toList ()); }

V predchádzajúcom príklade v podpise metódy znamená, že metóda sa bude zaoberať generickým typom T. To je potrebné, aj keď sa metóda vracia do platnosti.

Ako už bolo spomenuté vyššie, metóda sa môže zaoberať viac ako jedným generickým typom, v takom prípade je potrebné do podpisu metódy pridať všetky generické typy, napríklad ak chceme upraviť vyššie uvedenú metódu tak, aby sa zaoberala typom T a napíš G, malo by to byť napísané takto:

public static List fromArrayToList (T [] a, Function mapperFunction) {return Arrays.stream (a) .map (mapperFunction) .collect (Collectors.toList ()); }

Predávame funkciu, ktorá prevádza pole s prvkami typu T do zoznamu s prvkami typu G. Príkladom by mohla byť konverzia Celé číslo k jeho String zastúpenie:

@Test public void givenArrayOfIntegers_thanListOfStringReturnedOK () {Integer [] intArray = {1, 2, 3, 4, 5}; Zoznam stringList = Generics.fromArrayToList (intArray, Object :: toString); assertThat (stringList, hasItems ("1", "2", "3", "4", "5")); }

Stojí za zmienku, že odporúčaním spoločnosti Oracle je používať veľké písmeno na vyjadrenie všeobecného typu a zvoliť popisnejšie písmeno na vyjadrenie formálnych typov, napríklad v zbierkach Java. T sa používa pre typ, K pre kľúč, V. pre hodnotu.

3.1. Ohraničené generiká

Ako už bolo spomenuté, parametre typu môžu byť ohraničené. Ohraničené znamená „obmedzené„, Môžeme obmedziť typy, ktoré je možné prijať metódou.

Napríklad môžeme určiť, že metóda prijíma typ a všetky jeho podtriedy (horná hranica) alebo typ všetky jej nadtriedy (dolná hranica).

Na deklarovanie typu s horným ohraničením používame kľúčové slovo predlžuje za typom, za ktorým nasleduje horná hranica, ktorú chceme použiť. Napríklad:

verejný zoznam zArrayToList (T [] a) {...} 

Kľúčové slovo predlžuje sa tu používa na označenie toho typu T rozširuje hornú hranicu v prípade triedy alebo implementuje hornú hranicu v prípade rozhrania.

3.2. Viaceré hranice

Typ môže mať aj niekoľko horných ohraničení, a to nasledovne:

Ak jeden z typov, ktoré sú rozšírené o T je trieda (t.j. Číslo), musí byť na prvom mieste v zozname hraníc. V opačnom prípade to spôsobí chybu pri kompilácii.

4. Používanie zástupných znakov s generikami

Zástupné znaky sú v Jave zastúpené otáznikom „?”A používajú sa na označenie neznámeho typu. Zástupné znaky sú obzvlášť užitočné pri použití generík a je možné ich použiť ako typ parametra, najskôr však existuje znak dôležité poznámka na zváženie.

Je o tom známe Objekt je supertypom všetkých tried Java, je to však kolekcia Objekt nie je supertypom žiadnej zbierky.

Napríklad a Zoznam nie je supertypom Zoznam a priradenie premennej typu Zoznam na premennú typu Zoznam spôsobí chybu prekladača. To nezabráni možným konfliktom, ktoré môžu nastať, ak do tej istej kolekcie pridáme heterogénne typy.

Rovnaké pravidlo platí pre každú kolekciu typu a jeho podtypov. Zvážte tento príklad:

public static void paintAllBuildings (Zoznam budov) {buildings.forEach (Building :: paint); }

ak si predstavíme podtyp Budovanapríklad a House, nemôžeme použiť túto metódu so zoznamom House, aj keď House je podtyp Budova. Ak potrebujeme túto metódu použiť s typom Budovanie a so všetkými jeho podtypmi, potom môže čaro urobiť ohraničený zástupný znak:

public static void paintAllBuildings (Zoznam budov) {...} 

Teraz bude táto metóda fungovať s typom Budova a všetky jeho podtypy. Toto sa nazýva horný ohraničený zástupný znak kde typ Budova je horná hranica.

Zástupné znaky je možné určiť aj s dolnou hranicou, pričom neznámy typ musí byť nadtypom zadaného typu. Dolné hranice je možné určiť pomocou Super kľúčové slovo, za ktorým nasleduje konkrétny typ, napríklad znamená neznámy typ, ktorý je nadtriedou T (= T a všetci jeho rodičia).

5. Zadajte Vymazanie

Do Java boli pridané generiká, aby sa zaistila bezpečnosť typov a zaistilo sa, že generiká nebudú za behu réžie spôsobovať, kompilátor použije proces tzv. vymazanie typu na generikách v čase kompilácie.

Vymazanie typu odstráni všetky parametre typu a nahradí ho hranicami alebo Objekt ak je parameter typu neobmedzený. Bajtový kód po kompilácii teda obsahuje iba bežné triedy, rozhrania a metódy, čo zaisťuje, že sa nevyrábajú žiadne nové typy. Správne obsadenie sa aplikuje aj na Objekt typ v čase kompilácie.

Toto je príklad vymazania typu:

public List genericMethod (List list) {return list.stream (). collect (Collectors.toList ()); } 

Pri mazaní typu je to neobmedzený typ T sa nahrádza Objekt nasledovne:

// pre ilustráciu public List withErasure (List list) {return list.stream (). collect (Collectors.toList ()); } // ktoré v praxi vyústia do verejného Zoznamu sErasure (Zoznam), {return list.stream (). collect (Collectors.toList ()); } 

Ak je typ ohraničený, potom bude typ v čase kompilácie nahradený zviazaným:

public void genericMethod (T t) {...} 

by sa zmenilo po kompilácii:

public void genericMethod (budova t) {...}

6. Generické a primitívne dátové typy

Obmedzením generík v Jave je, že parameter typu nemôže byť primitívny typ.

Napríklad nasledujúce sa nezhromažďuje:

Zoznam zoznam = nový ArrayList (); list.add (17);

Aby sme pochopili, prečo primitívne dátové typy nefungujú, pamätajme na to generiká sú funkciou kompilácie, čo znamená, že parameter typu je vymazaný a všetky generické typy sú implementované ako typ Objekt.

Ako príklad sa pozrime na pridať metóda zoznamu:

Zoznam zoznam = nový ArrayList (); list.add (17);

Podpis pridať metóda je:

boolovský prídavok (E e);

A bude zostavený do:

boolean add (Objekt e);

Parametre typu preto musia byť prevediteľné na Objekt. Pretože primitívne typy sa nerozširujú Objekt, nemôžeme ich použiť ako parametre typu.

Java však poskytuje primitívne typy v škatuľke spolu s automatickým vybaľovaním a rozbaľovaním na ich rozbalenie:

Celé číslo a = 17; int b = a; 

Ak teda chceme vytvoriť zoznam, ktorý bude obsahovať celé čísla, môžeme použiť obal:

Zoznam zoznam = nový ArrayList (); list.add (17); int first = list.get (0); 

Zkompilovaný kód bude ekvivalentom:

Zoznam zoznam = nový ArrayList (); list.add (Integer.valueOf (17)); int first = ((Integer) list.get (0)). intValue (); 

Budúce verzie Java môžu umožňovať primitívne dátové typy pre generiká. Cieľom projektu Valhalla je zlepšiť spôsob zaobchádzania s generikami. Cieľom je implementácia špecializácie generík, ako je opísané v JEP 218.

7. Záver

Java Generics je silným doplnkom jazyka Java, pretože uľahčuje prácu programátora a je menej náchylný na chyby. Generiká vynucujú správnosť typu v čase kompilácie a čo je najdôležitejšie, umožňujú implementáciu všeobecných algoritmov bez toho, aby spôsobovali ďalšie réžie našim aplikáciám.

Zdrojový kód, ktorý je priložený k článku, je k dispozícii na GitHub.


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