Java Generics interjúkérdések (+ válaszok)

Ez a cikk egy sorozat része: • Java Collections interjúkérdések

• Java típusú rendszerinterjúkérdések

• Java egyidejű interjúkérdések (+ válaszok)

• Java osztály felépítése és inicializálása interjúkérdések

• Java 8 interjúkérdések (+ válaszok)

• Memóriakezelés Java interjúkérdésekben (+ válaszok)

• Java Generics Interjúkérdések (+ Válaszok) (aktuális cikk) • Java Flow Control Interjúkérdések (+ Válaszok)

• Java kivételek interjúkérdések (+ válaszok)

• Java Annotations Interjúkérdések (+ Válaszok)

• A tavaszi keretrendszer legfontosabb interjúi

1. Bemutatkozás

Ebben a cikkben áttekintünk néhány példát a Java generics interjúval kapcsolatos kérdésekre és válaszokra.

A generikumok a Java egyik alapkoncepciója, amelyet először a Java 5-ben vezettek be. Emiatt majdnem az összes Java-kódbázis felhasználja ezeket, szinte garantálva, hogy egy fejlesztő valamikor belefut nekik. Éppen ezért elengedhetetlen a helyes megértésük, és ezért több mint valószínű, hogy rájuk kérdeznek egy interjú során.

2. Kérdések

Q1. Mi az általános típusú paraméter?

típus a neve osztály vagy felület. Amint azt a név is sugallja, általános típusú paraméter az, amikor a típus osztály, metódus vagy interfész deklaráció paramétereként használható.

Kezdjük egy egyszerű példával, amely generikumok nélkül mutat be ezt:

public interface Consumer {public void consume (String paraméter)}

Ebben az esetben a fogyaszt() módszer az Húr. Nem paraméterezhető és nem konfigurálható.

Most cseréljük ki Húr írja be egy általános típussal, amelyet hívunk T. Egyezmény szerint így nevezik:

public interface Consumer {public void consume (T paraméter)}

Amikor megvalósítjuk fogyasztóinkat, biztosíthatjuk a típus hogy azt akarjuk, hogy érvként fogyasszon. Ez egy általános típusú paraméter:

public class IntegerConsumer implementálja a Consumer {public void consume (Integer paraméter)}

Ebben az esetben most egész számokat fogyaszthatunk. Ezt felcserélhetjük típus bármire is szükségünk van.

Q2. Milyen előnyei vannak az általános típusok használatának?

A generikus gyógyszerek használatának egyik előnye a gipszek elkerülése és a típusbiztonság biztosítása. Ez különösen akkor hasznos, ha gyűjteményekkel dolgozik. Bemutassuk ezt:

Lista lista = new ArrayList (); list.add ("foo"); Object o = list.get (0); Karakterlánc foo = (húr) o;

Példánkban a listánk elemtípusa a fordító számára ismeretlen. Ez azt jelenti, hogy egyetlen dolog garantálható, hogy tárgyról van szó. Tehát amikor lekérjük elemünket, egy Tárgy az, amit visszakapunk. Mint a kód készítői, tudjuk, hogy a Húr, de a problémát kifejezetten orvosolni kell az egyiket. Ez nagy zajt és kazántáblát eredményez.

Ezután, ha elkezdünk gondolkodni a kézi hibázás helyén, az öntési probléma súlyosbodik. Mi lenne, ha véletlenül lenne egy Egész szám a listánkban?

list.add (1) Objektum o = list.get (0); Karakterlánc foo = (húr) o;

Ebben az esetben a ClassCastException futás közben, mint egy Egész szám nem lehet rávetni Húr.

Most próbáljuk megismételni magunkat, ezúttal generikus gyógyszerekkel:

Lista lista = new ArrayList (); list.add ("foo"); Karakterlánc o = list.get (0); // Nincs leadott egész szám foo = list.get (0); // Összeállítási hiba

Ahogy látjuk, a generikumok használatával lefordítunk egy típusellenőrzést, amely megakadályozza ClassCastExceptions és feleslegessé teszi az öntést.

A másik előny a kódmásolás elkerülése. Generikumok nélkül ugyanazt a kódot kell másolnunk és beillesztenünk, de különböző típusokhoz. A generikus gyógyszerekkel ezt nem kell megtennünk. Akár olyan algoritmusokat is megvalósíthatunk, amelyek általános típusokra vonatkoznak.

Q3. Mi a típusú törlés?

Fontos felismerni, hogy az általános típusú információk csak a fordító számára állnak rendelkezésre, a JVM-nek nem. Más szavakkal, a típusú törlés azt jelenti, hogy az általános típusú információk a JVM számára futás közben nem érhetők el, csak az összeállítás ideje.

A fő megvalósítási lehetőségek mögött álló érvelés egyszerű - megőrzi a visszafelé kompatibilitást a Java régebbi verzióival. Amikor egy általános kódot bájtkódba fordítanak, akkor olyan lesz, mintha az általános típus soha nem létezett volna. Ez azt jelenti, hogy az összeállítás:

  1. Cserélje le az általános típusokat objektumokra
  2. Cserélje le a bekötött típusokat (ezekről bővebben egy későbbi kérdésben) az első kötött osztályra
  3. Helyezze be a leadások megfelelőjét általános objektumok lekérésekor.

Fontos megérteni a típus törlését. Ellenkező esetben a fejlesztő összezavarodhat, és azt gondolhatja, hogy futás közben képes lenne megszerezni a típust:

public foo (Fogyasztói fogyasztó) {Type type = consumer.getGenericTypeParameter ()}

A fenti példa egy álkód megfelelője annak, hogy a dolgok milyenek lehetnek a törlés nélkül, de sajnos ez lehetetlen. Még egyszer, az általános típusú információk futás közben nem érhetők el.

Q4. Ha az objektum indításakor általános típus elhagyásra kerül, a kód továbbra is összeáll?

Mivel a Java 5 előtt nem léteztek generikusok, lehetséges, hogy egyáltalán nem használjuk őket. Például a generikusokat utólag beépítették a legtöbb Java osztályba, például a gyűjteményekbe. Ha az első kérdésből megnézzük a listánkat, akkor látni fogjuk, hogy már van példa az általános típus kihagyására:

Lista lista = new ArrayList ();

Annak ellenére, hogy képes fordítani, mégis valószínű, hogy figyelmeztetést kap a fordító. Ennek oka az, hogy elveszítjük azt az extra fordítási időbeli ellenőrzést, amelyet a generikus gyógyszerek használatával kapunk.

A lényeg, amire emlékezni kell míg a visszafelé kompatibilitás és a típusok törlése lehetővé teszi az általános típusok kihagyását, ez rossz gyakorlat.

Q5. Miben különbözik egy általános módszer az általános típustól?

Általános módszer az, amikor egy típusú paramétert bevisznek egy módszerbe,e módszer keretein belül él. Próbáljuk ki ezt egy példával:

public static T returnType (T argumentum) {return argumentum; }

Használtunk statikus módszert, de használhattunk volna nem statikus módszert is, ha szeretnénk. A (következõ kérdésben tárgyalt) következtetés kihasználásával erre hivatkozhatunk, mint bármely más szokásos módszerre, anélkül, hogy tennünk kellene bármilyen típusú argumentumot.

Q6. Mi a típusú következtetés?

A típus következtetése az, amikor a fordító megnézheti a metódus argumentumának típusát egy általános típus kikövetkeztetésére. Például, ha átmentünk T egy módszerre, amely visszatér T, akkor a fordító kitalálja a visszatérés típusát. Próbáljuk ki ezt az előző kérdés általános módszerének felhasználásával:

Egész következtetettInteger = returnType (1); String inferredString = returnType ("String");

Mint láthatjuk, nincs szükség szereplőkre, és nem kell átadni semmilyen általános típusú argumentumot. Az argumentumtípus csak a visszatérés típusára következtet.

Q7. Mi a határolt típusú paraméter?

Eddig minden kérdésünk általános típusú érvekkel foglalkozott, amelyek nem korlátozottak. Ez azt jelenti, hogy általános típusú argumentumaink tetszőleges típusúak lehetnek.

Ha korlátozott paramétereket használunk, korlátozzuk azokat a típusokat, amelyek általános típusú argumentumként használhatók.

Példaként mondjuk azt, hogy általános típusunkat mindig arra kell kényszeríteni, hogy az állatok alosztálya legyen:

nyilvános absztrakt osztály Cage {abstract void addAnimal (T állat)}

A kiterjesztések használatával, erőltetjük T hogy az állatok egy alosztálya legyen. Ezután lehet egy macskaketrecünk:

Cage catCage;

De nem lehetett tárgyakkalitka, mivel a tárgy nem egy állat alosztálya:

Cage objectCage; // Összeállítási hiba

Ennek egyik előnye, hogy az állat összes módszere elérhető a fordító számára. Tudjuk, hogy típusunk kiterjeszti, ezért írhatunk egy általános algoritmust, amely bármely állaton működik. Ez azt jelenti, hogy nem kell reprodukálnunk a módszerünket különböző állat-alosztályokra:

public void firstAnimalJump () {T állat = állatok.get (0); állat.ugrás (); }

Q8. Lehetséges-e többszörösen korlátozott típusú paramétert deklarálni?

Lehetőség van több határ deklarálására általános típusaink esetében. Előző példánkban egyetlen kötést adtunk meg, de kívánság szerint többet is megadhatunk:

nyilvános absztrakt osztály Cage

Példánkban az állat osztály és összehasonlítható felület. Most a típusunknak tiszteletben kell tartania mindkét felső határt. Ha a típusunk az állatok egy alosztálya lenne, de nem valósítanánk meg összehasonlítható elemeket, akkor a kód nem állna össze. Arra is érdemes emlékezni, hogy ha az egyik felső határ osztály, akkor annak kell lennie az első érvnek.

Q9. Mi a helyettesítő karakter típusa?

A helyettesítő karakter típus ismeretlen típus. A kérdőjellel a következőképpen robbantják fel:

public static void consumeListOfWildcardType (Lista lista)

Itt meghatározunk egy listát, amely lehet bármelyik típus. Átvihetnénk bármi listáját ebbe a módszerbe.

Q10. Mi a felső korlátozott helyettesítő karakter?

A felső határolt helyettesítő karakter az, amikor a helyettesítő karaktertípus egy konkrét típusból öröklődik. Ez különösen akkor hasznos, ha gyűjteményekkel és örökléssel dolgozunk.

Próbáljuk meg bemutatni ezt egy olyan farmosztályral, amely először helyettesítő karakter nélkül tárolja az állatokat:

public class Farm {private List állatok; public void addAnimals (Gyűjtemény newAnimals) {animals.addAll (newAnimals); }}

Ha több állatosztályunk lenne, mint például a macska és a kutya, tévesen feltételezhetjük, hogy mindet hozzáadhatjuk farmunkhoz:

farm.addIllatok (macskák); // Összeállítási hiba farm.addAnimals (kutyák); // Összeállítási hiba

A fordító ugyanis elvárja a betontípusú állatok gyűjtését, nem egy alosztály.

Vezessünk be egy felső határolt helyettesítő karaktert az állatok hozzáadásához:

public void addAnimals (Gyűjtemény newAnimals)

Ha most újra megpróbáljuk, a kódunk összeáll. Ez azért van, mert most azt mondjuk a fordítónak, hogy fogadja el az állatok bármely altípusának gyűjteményét.

Q11. Mi az a korlátlan helyettesítő karakter?

A nem korlátozott helyettesítő helyettesítő helyettesítő nincs felső vagy alsó határ, amely bármilyen típust képviselhet.

Fontos tudni azt is, hogy a helyettesítő karaktertípus nem azonos az objektummal. Ennek oka, hogy a helyettesítő karakter bármilyen típusú lehet, míg az objektumtípus kifejezetten egy objektum (és nem lehet egy objektum alosztálya). Bemutassuk ezt egy példával:

List wildcardList = new ArrayList (); List objectList = new ArrayList (); // Összeállítási hiba

Ismét az az oka, hogy a második sor nem fordítja le, hogy objektumok listája szükséges, nem pedig karakterláncok listája. Az első sor azért áll össze, mert bármilyen ismeretlen típusú lista elfogadható.

Q12. Mi az alsó határú helyettesítő?

Alsó határolt helyettesítő karakter az, amikor a felső határ helyett az alsó határt adjuk meg a szuper kulcsszó. Más szavakkal, egy alacsonyabban behatárolt helyettesítő azt jelenti, hogy arra kényszerítjük a típust, hogy a mi korlátozott típusunk szuperosztálya legyen. Próbáljuk ki ezt egy példával:

public static void addDogs (Lista lista) {list.add (új kutya ("tom"))}}

Használva szuper, az objektumok listáján meghívhatjuk az addDogs-ot:

ArrayList objektumok = new ArrayList (); addDogs (objektumok);

Ennek van értelme, mivel egy tárgy az állatok szuperosztálya. Ha nem az alsó határolású helyettesítő karaktert használnánk, a kód nem állna össze, mivel az objektumok listája nem az állatok listája.

Ha belegondolunk, nem tudnánk felvenni a kutyát az állatok bármely alosztályának, például macskák vagy akár kutyák listájára. Csak egy szuperosztályú állat. Például ez nem állítja össze:

ArrayList objektumok = new ArrayList (); addDogs (objektumok);

Q13. Mikor választaná az alsó és a felső határú típus használatát?

Gyűjtemények kezelésekor a PECS a felső vagy alsó határolt helyettesítő karakterek közötti választás általános szabálya. A PECS jelentése termelő kiterjeszti, fogyasztói szuper.

Ez könnyen bizonyítható néhány standard Java interfész és osztály használatával.

Producer kiterjeszti csak azt jelenti, hogy ha generikus típusú gyártót hoz létre, akkor használja a kiterjed kulcsszó. Próbáljuk meg ezt az elvet alkalmazni egy gyűjteményre, hogy megtudjuk, miért van értelme:

public static void makeLotsOfNoise (Állatok felsorolása) {animals.forEach (Animal :: makeNoise); }

Itt szeretnénk felhívni zajt csinálni() gyűjteményünk minden egyes állatán. Ez azt jelenti, hogy kollekciónk termelő, mivel csak annyit csinálunk, hogy visszaküldjük az állatokat, hogy elvégezhessük a műveletünket. Ha megszabadultunk volna kiterjed, nem tudnánk továbbadni a macskák listáján, kutyák vagy az állatok bármely más alosztálya. A producer kiterjeszti elv alkalmazásával a lehető legnagyobb rugalmassággal rendelkezünk.

Fogyasztó szuper az ellenkezőjét jelenti termelő kiterjeszti. Ez csak annyit jelent, hogy ha valamivel foglalkozunk, ami elemeket fogyaszt, akkor a szuper kulcsszó. Ezt az előző példánk megismétlésével bizonyíthatjuk:

public static void addCats (List állatok) {állatok.add (új Macska ()); }

Csak kiegészítjük az állatok listáját, így az állatok listája fogyasztó. Ezért használjuk a szuper kulcsszó. Ez azt jelenti, hogy átadhatnánk az állatok bármely szuperosztályának listáját, de nem egy alosztályt. Például, ha megpróbáljuk átadni a kutyák vagy macskák listáját, akkor a kód nem áll össze.

Az utolsó megfontolandó dolog az, hogy mit tegyünk, ha a gyűjtemény mind fogyasztó, mind termelő. Erre példa lehet egy gyűjtemény, ahol elemeket is hozzáadnak és eltávolítanak. Ebben az esetben korlátlan helyettesítő karaktert kell használni.

Q14. Vannak-e olyan helyzetek, amikor általános típusú információk érhetők el futás közben?

Van egy olyan helyzet, amikor egy általános típus futás közben elérhető. Ekkor egy általános típus az osztály aláírásának része, így:

nyilvános osztályú CatCage megvalósítja Cage-et

A reflexió használatával megkapjuk ezt a típusú paramétert:

(Osztály) ((ParameterizedType) getClass () .getGenericSuperclass ()). GetActualTypeArguments () [0];

Ez a kód kissé törékeny. Például attól függ, hogy milyen típusú paramétert határoznak meg a közvetlen szuperosztályon. De azt mutatja, hogy a JVM rendelkezik ilyen típusú információkkal.

Következő » Java Flow Control interjúkérdések (+ válaszok) « Korábbi memóriakezelés a Java interjúban (+ válaszok)