Java Generics Interview Questions (+ Answers)

Tento článok je súčasťou série: • Otázky týkajúce sa rozhovorov o zbierkach Java

• Dotazy týkajúce sa systému typu Java

• Otázky týkajúce sa rozhovorov o súbehu Java (+ odpovede)

• Otázky týkajúce sa štruktúry triedy Java a inicializácie

• Otázky týkajúce sa rozhovoru s Java 8 (+ odpovede)

• Správa pamäte v otázkach týkajúcich sa rozhovorov Java (+ odpovede)

• Otázky týkajúce sa rozhovoru s generikou Java (+ odpovede) (aktuálny článok) • Otázky týkajúce sa rozhovoru týkajúceho sa riadenia toku Java (+ odpovede)

• Otázky týkajúce sa rozhovorov o výnimkách jazyka Java (+ odpovede)

• Dotazy k anotáciám Java (+ odpovede)

• Najlepšie otázky týkajúce sa jarného rámcového rozhovoru

1. Úvod

V tomto článku si ukážeme niekoľko príkladov otázok a odpovedí na otázky týkajúce sa generických jazykov Java.

Generiká sú základným konceptom v Jave, ktorý bol prvýkrát predstavený v Jave 5. Z tohto dôvodu ich budú využívať takmer všetky databázové základne Javy, čo takmer zaručuje, že do nich vývojár v určitom okamihu narazí. Preto je nevyhnutné správne im porozumieť, a preto je pravdepodobné, že sa ich na vás počas pohovoru bude pýtať.

2. Otázky

Q1. Čo je to parameter všeobecného typu?

Typ je názov a trieda alebo rozhranie. Ako vyplýva z názvu, parameter generického typu je, keď a typu možno použiť ako parameter v deklarácii triedy, metódy alebo rozhrania.

Začnime jednoduchým príkladom, ktorý nemá generiku, aby sme to demonštrovali:

verejné rozhranie Spotrebiteľ {public void consume (parameter String)}

V tomto prípade je typ parametra metódy konzumovať () metóda je String. Nie je parametrizovateľný a konfigurovateľný.

Teraz nahraďme našu String typ s generickým typom, ktorý budeme nazývať T. Je pomenovaný takto podľa konvencie:

verejné rozhranie Zákazník {public void consume (parameter T)}

Keď implementujeme nášho spotrebiteľa, môžeme poskytnúť typu že chceme, aby to spotrebovalo ako argument. Toto je parameter všeobecného typu:

public class IntegerConsumer implementuje Consumer {public void consume (Integer parameter)}

V takom prípade teraz môžeme spotrebovať celé čísla. Môžeme to vymeniť typu na čokoľvek požadujeme.

Q2. Aké sú výhody použitia všeobecných typov?

Jednou z výhod používania generík je vyhýbanie sa vrhnutiu a zaistenie bezpečnosti typu. To je obzvlášť užitočné pri práci so zbierkami. Poďme si to demonštrovať:

Zoznam zoznam = nový ArrayList (); list.add ("foo"); Objekt o = list.get (0); Reťazec foo = (Reťazec) o;

V našom príklade je typ prvku v našom zozname pre kompilátor neznámy. To znamená, že zaručiť sa dá iba to, že ide o objekt. Takže keď načítame náš prvok, Objekt je to, čo dostaneme späť. Ako autori kódu vieme, že je to Reťazec, ale musíme svoj objekt odovzdať jednému, aby sa problém jednoznačne vyriešil. To produkuje veľa hluku a varu.

Ďalej, ak začneme myslieť na priestor pre manuálne chyby, problém s vrhaním sa zhoršuje. Čo keby sme náhodou mali Celé číslo v našom zozname?

list.add (1) Objekt o = list.get (0); Reťazec foo = (Reťazec) o;

V takom prípade by sme dostali ClassCastException za behu, ako Celé číslo nie je možné obsadiť do String.

Skúsme si to zopakovať, tentokrát s použitím generík:

Zoznam zoznam = nový ArrayList (); list.add ("foo"); Reťazec o = list.get (0); // Žiadne obsadenie Celé číslo foo = list.get (0); // Chyba kompilácie

Ako vidíme, pomocou generík máme kontrolu typu kompilácie, ktorá zabraňuje ClassCastExceptions a odstraňuje potrebu liatia.

Ďalšou výhodou je vyhnúť sa duplikácii kódu. Bez generík musíme kopírovať a vkladať rovnaký kód, ale pre rôzne typy. Pri generikách to robiť nemusíme. Môžeme dokonca implementovať algoritmy, ktoré sa vzťahujú na všeobecné typy.

Q3. Čo je to vymazanie typu?

Je dôležité si uvedomiť, že informácie generického typu sú dostupné iba pre kompilátor, nie pre JVM. Inými slovami„vymazanie typu“ znamená, že informácie o všeobecnom type nie sú k dispozícii pre JVM za behu, iba pri kompilácii.

Dôvody výberu hlavnej implementácie sú jednoduché - zachovanie spätnej kompatibility so staršími verziami Java. Keď sa generický kód skompiluje do bytecode, bude to, akoby generický typ nikdy neexistoval. To znamená, že kompilácia:

  1. Nahraďte všeobecné typy objektmi
  2. Nahraďte ohraničené typy (viac o nich v neskoršej otázke) prvou ohraničenou triedou
  3. Pri načítaní všeobecných objektov vložte ekvivalent obsadenia.

Je dôležité porozumieť vymazaniu typu. V opačnom prípade môže byť vývojár zmätený a bude si myslieť, že by bol schopný tento typ získať za behu:

public foo (spotrebiteľský spotrebiteľ) {Type type = consumer.getGenericTypeParameter ()}

Vyššie uvedený príklad je ekvivalentom pseudokódu k tomu, ako by veci mohli vyzerať bez vymazania typu, ale bohužiaľ je to nemožné. Ešte raz, informácie o všeobecnom type nie sú za behu k dispozícii.

Q4. Ak sa pri inštancii objektu vynechá všeobecný typ, bude sa kód stále kompilovať?

Pretože generické verzie pred Java 5 neexistovali, je možné ich vôbec nepoužívať. Napríklad generiká boli dovybavené väčšinou štandardných tried Java, ako sú napríklad zbierky. Ak sa pozrieme na náš zoznam z otázky jedna, uvidíme, že už máme príklad vynechania generického typu:

Zoznam zoznam = nový ArrayList ();

Napriek možnosti kompilácie je stále pravdepodobné, že od kompilátora dôjde k varovaniu. Je to tak preto, lebo prichádzame o ďalšiu kontrolu kompilácie, ktorú získavame z používania generík.

Nezabudnite, že zatiaľ čo spätná kompatibilita a mazanie typov umožňujú vynechať všeobecné typy, je zlým zvykom.

Q5. Ako sa líši generická metóda od generického typu?

Všeobecná metóda je tam, kde sa do metódy zavádza parameter typu,žijúci v rámci rozsahu tejto metódy. Skúsme to na príklade:

public static T returnType (T argument) {návratový argument; }

Použili sme statickú metódu, ale mohli sme použiť aj nestatickú metódu, ak si prajeme. Využitím odvodenia typu (obsiahnutého v nasledujúcej otázke) to môžeme vyvolať ako každú bežnú metódu, bez toho aby sme pri tom museli špecifikovať akékoľvek argumenty typu.

Q6. Čo je odvodenie typu?

Odvodenie typu je, keď sa kompilátor môže pozrieť na typ argumentu metódy a odvodiť tak všeobecný typ. Napríklad keby sme prešli dnu T na metódu, ktorá sa vráti T, potom môže kompilátor zistiť návratový typ. Vyskúšame to pomocou našej všeobecnej metódy z predchádzajúcej otázky:

Celé číslo inferredInteger = returnType (1); String inferredString = returnType ("String");

Ako vidíme, nie je potrebné obsadenie a nie je potrebné uvádzať nijaký argument generického typu. Typ argumentu odvodzuje iba návratový typ.

Q7. Čo je parameter ohraničeného typu?

Zatiaľ všetky naše otázky zahŕňali argumenty všeobecných typov, ktoré sú neobmedzené. To znamená, že našimi argumentmi generického typu môže byť akýkoľvek typ, ktorý chceme.

Keď používame ohraničené parametre, obmedzujeme typy, ktoré je možné použiť ako argumenty všeobecných typov.

Ako príklad povedzme, že chceme, aby náš generický typ bol vždy podtriedou zvierat:

verejná abstraktná trieda Klietka {abstract void addAnimal (T animal)}

Použitím extendov, nútime T byť podtriedou zvierat. Potom by sme mohli mať klietku s mačkami:

Klietka catCage;

Nemohli sme však mať klietku objektov, pretože objekt nie je podtriedou zvieraťa:

Klietka objectCage; // Chyba kompilácie

Jednou z výhod toho je, že kompilátor má k dispozícii všetky metódy animal. Vieme, že náš typ to rozširuje, takže by sme mohli napísať všeobecný algoritmus, ktorý funguje na akomkoľvek zvierati. To znamená, že nemusíme reprodukovať našu metódu pre rôzne podtriedy zvierat:

public void firstAnimalJump () {T animal = animals.get (0); animal.jump (); }

Q8. Je možné deklarovať viacnásobný parameter ohraničeného typu?

Vyhlásenie viacerých hraníc pre naše generické typy je možné. V našom predchádzajúcom príklade sme zadali jednu väzbu, ale ak chceme, mohli by sme určiť aj viac:

verejná abstraktná trieda Klietka

V našom príklade je zviera trieda a porovnateľné je rozhranie. Náš typ teraz musí rešpektovať obidve tieto horné hranice. Keby náš typ bol podtriedou zvierat, ale neimplementoval by sa porovnateľne, potom by sa kód nezostavil. Je tiež potrebné pripomenúť, že ak je jednou z horných hraníc trieda, musí to byť prvý argument.

Q9. Čo je typ zástupného znaku?

Zástupný typ predstavuje neznámu typu. Je odpálený otáznikom nasledovne:

public static void consumeListOfWildcardType (zoznam)

Tu upresňujeme zoznam, ktorý môže byť akýkoľvek typu. Do tejto metódy by sme mohli vložiť zoznam všetkého.

Q10. Čo je to horný ohraničený zástupný znak?

Zástupný znak s hornou hranicou je, keď typ zástupného znaku dedí od konkrétneho typu. To je obzvlášť užitočné pri práci so zbierkami a dedičstvom.

Skúsme to predviesť na triede farmy, ktorá bude ukladať zvieratá, najskôr bez zástupného typu:

public class Farm {private List animals; public void addAnimals (Zbierka newAnimals) {animals.addAll (newAnimals); }}

Keby sme mali viac podtried zvierat, ako mačka a pes, môžeme urobiť nesprávny predpoklad, že ich môžeme všetky pridať na našu farmu:

farm.addZvieratá (mačky); // Chyba kompilácie farm.addAnimals (dogs); // Chyba kompilácie

Je to preto, že kompilátor očakáva zbierku konkrétneho zvieraťa, nie jeden, ktorý podtriedy.

Teraz predstavíme horný ohraničený zástupný znak pre našu metódu pridania zvierat:

public void addAnimals (Zbierka newAnimals)

Ak to skúsime znova, náš kód sa skompiluje. Je to tak preto, lebo kompilátoru teraz hovoríme, aby prijal zbierku akýchkoľvek podtypov zvierat.

Q11. Čo je neobmedzený zástupný znak?

Neohraničený zástupný znak je zástupný znak bez hornej alebo dolnej hranice, ktorý môže predstavovať akýkoľvek typ.

Je tiež dôležité vedieť, že typ zástupného znaku nie je synonymom objektu. Je to preto, že zástupný znak môže byť ľubovoľného typu, zatiaľ čo typ objektu je konkrétne objekt (a nemôže byť podtriedou objektu). Ukážme si to na príklade:

List wildcardList = new ArrayList (); List objectList = new ArrayList (); // Chyba kompilácie

Dôvod, ktorý druhý riadok nekompiluje, je opäť ten, že sa vyžaduje zoznam objektov, nie zoznam reťazcov. Prvý riadok sa zostavuje, pretože je prijateľný zoznam všetkých neznámych typov.

Q12. Čo je zástupný znak s dolným ohraničením?

Zástupný znak dolnej hranice je, keď namiesto poskytnutia hornej hranice poskytneme dolnú hranicu pomocou Super kľúčové slovo. Inými slovami, zástupný znak s dolným ohraničením znamená, že nútime typ, aby bol nadtriedou nášho ohraničeného typu. Skúsme to na príklade:

verejné statické neplatné addDogs (zoznam) {list.add (nový pes ("tom"))}

Používaním Super, mohli by sme zavolať addDogs na zoznam objektov:

ArrayList objekty = nový ArrayList (); addDogs (objekty);

To dáva zmysel, pretože objekt je nadtriedou zvierat. Keby sme nepoužili zástupný znak s dolným ohraničením, kód by sa nezostavil, pretože zoznam objektov nie je zoznamom zvierat.

Keby sme sa nad tým zamysleli, nemohli by sme pridať psa do zoznamu žiadnych podtried zvierat, ako sú mačky alebo dokonca psy. Iba nadtrieda zvierat. Napríklad by sa to nezostavilo:

ArrayList objekty = nový ArrayList (); addDogs (objekty);

Q13. Kedy by ste sa rozhodli použiť typ s dolným ohraničením a typ s horným ohraničením?

Pri práci so zbierkami je bežným pravidlom pre výber medzi zástupnými znakmi s horným alebo dolným ohraničením PECS. PECS znamená výrobca rozširuje, spotrebiteľ super.

To sa dá ľahko preukázať použitím niektorých štandardných rozhraní a tried Java.

Výrobca sa rozširuje znamená iba to, že ak vytvárate producenta generického typu, použite znak predlžuje kľúčové slovo. Skúsme tento princíp aplikovať na zbierku, aby sme zistili, prečo to má zmysel:

public static void makeLotsOfNoise (zoznam zvierat) {animals.forEach (Animal :: makeNoise); }

Tu chceme volať Robit hluk() na každé zviera v našej zbierke. To znamená, že naša kolekcia je producentom, pretože všetko, čo s tým robíme, je to, aby sme vracali zvieratá, aby sme mohli vykonať našu operáciu. Keby sme sa zbavili predlžuje, neboli by sme schopní odovzdať zoznamy mačiek, psy alebo akékoľvek iné podtriedy zvierat. Použitím princípu rozšírenia výrobcu máme maximálnu možnú flexibilitu.

Spotrebiteľ super znamená opak výrobca rozširuje. Znamená to iba to, že ak máme do činenia s niečím, čo konzumuje prvky, mali by sme používať Super kľúčové slovo. Môžeme to demonštrovať opakovaním nášho predchádzajúceho príkladu:

public static void addCats (List animals) {animals.add (new Cat ()); }

Iba pridávame do nášho zoznamu zvierat, takže náš zoznam zvierat je spotrebiteľ. Preto používame Super kľúčové slovo. Znamená to, že by sme mohli odovzdať zoznam akejkoľvek nadtriedy zvierat, ale nie podtriedy. Napríklad, ak by sme sa pokúsili odovzdať zoznam psov alebo mačiek, kód by sa nezostavil.

Posledná vec, ktorú je potrebné zvážiť, je čo robiť, ak je zbierka spotrebiteľom aj výrobcom. Príkladom toho môže byť kolekcia, kde sa prvky pridávajú aj odoberajú. V takom prípade by sa mal použiť neobmedzený zástupný znak.

Q14. Existujú situácie, keď sú informácie o všeobecnom type dostupné za behu?

Existuje jedna situácia, keď je generický typ k dispozícii za behu programu. To je prípad, keď je generický typ súčasťou podpisu triedy takto:

verejná trieda CatCage implementuje Cage

Použitím odrazu získame tento typový parameter:

(Class) ((ParameterizedType) getClass () .getGenericSuperclass ()). GetActualTypeArguments () [0];

Tento kód je trochu krehký. Závisí to napríklad od parametra typu, ktorý je definovaný v bezprostrednej nadtriede. Ale ukazuje, že JVM má tento typ informácií.

Ďalšie » Dotazy týkajúce sa riadenia toku Java (+ odpovede) « Predchádzajúce otázky týkajúce sa správy pamäte v prostredí Java Interview (+ odpovede)

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