Java Generics sollicitatievragen (+ antwoorden)

Dit artikel maakt deel uit van een reeks: • Interviewvragen over Java Collections

• Vragen over het Java Type System-interview

• Vragen over Java Concurrency-sollicitatiegesprekken (+ antwoorden)

• Vragen over Java-klassenstructuur en initialisatie

• Java 8 sollicitatievragen (+ antwoorden)

• Geheugenbeheer in Java-interviewvragen (+ antwoorden)

• Java Generics interviewvragen (+ antwoorden) (huidig ​​artikel) • Java Flow Control interviewvragen (+ antwoorden)

• Interviewvragen over Java-uitzonderingen (+ antwoorden)

• Vragen over Java-annotaties tijdens sollicitatiegesprekken (+ antwoorden)

• Topvragen tijdens het Spring Framework-interview

1. Inleiding

In dit artikel zullen we enkele voorbeelden van Java-generieke interviewvragen en antwoorden bespreken.

Generics zijn een kernconcept in Java, voor het eerst geïntroduceerd in Java 5. Hierdoor zullen bijna alle Java-codebases er gebruik van maken, wat bijna garandeert dat een ontwikkelaar ze op een gegeven moment tegenkomt. Daarom is het essentieel om ze correct te begrijpen, en daarom wordt er hoogstwaarschijnlijk naar gevraagd tijdens een sollicitatiegesprek.

2. Vragen

V1. Wat is een algemene typeparameter?

Type is de naam van een klasse of koppel. Zoals geïmpliceerd door de naam, een generieke typeparameter is wanneer een type kan worden gebruikt als parameter in een klasse-, methode- of interfacedeclaratie.

Laten we beginnen met een eenvoudig voorbeeld, een zonder generieke geneesmiddelen, om dit te demonstreren:

openbare interface Consument {openbare leegte consumeren (tekenreeksparameter)}

In dit geval is het methodeparametertype van de consumeren() methode is Draad. Het is niet geparametriseerd en niet configureerbaar.

Laten we nu onze vervangen Draad typ met een generiek type dat we zullen noemen T. Het wordt volgens afspraak zo genoemd:

openbare interface Consument {openbare leegte consumeren (T-parameter)}

Wanneer we onze consument implementeren, kunnen we de type die we willen gebruiken als argument. Dit is een algemene typeparameter:

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

In dit geval kunnen we nu gehele getallen consumeren. We kunnen dit ruilen type voor alles wat we nodig hebben.

Q2. Wat zijn enkele voordelen van het gebruik van generieke typen?

Een voordeel van het gebruik van generieke geneesmiddelen is het vermijden van afgietsels en het bieden van typeveiligheid. Dit is vooral handig bij het werken met verzamelingen. Laten we dit demonstreren:

Lijstlijst = nieuwe ArrayList (); list.add ("foo"); Object o = lijst.get (0); String foo = (String) o;

In ons voorbeeld is het elementtype in onze lijst onbekend bij de compiler. Dit betekent dat het enige dat kan worden gegarandeerd, is dat het een object is. Dus als we ons element terughalen, een Voorwerp is wat we terugkrijgen. Als auteurs van de code weten we dat het een Draad, maar we moeten ons doel naar iemand richten om het probleem expliciet op te lossen. Dit levert veel lawaai en boilerplate op.

Als we vervolgens beginnen na te denken over de ruimte voor handmatige fouten, wordt het castingprobleem erger. Wat als we per ongeluk een Geheel getal in onze lijst?

list.add (1) Object o = list.get (0); String foo = (String) o;

In dit geval zouden we een ClassCastException tijdens runtime, als een Geheel getal kan niet worden gecast Draad.

Laten we nu proberen onszelf te herhalen, deze keer met generieke geneesmiddelen:

Lijstlijst = nieuwe ArrayList (); list.add ("foo"); String o = lijst.get (0); // Geen cast Integer foo = list.get (0); // Compilatiefout

Zoals we kunnen zien, door generieke geneesmiddelen te gebruiken, hebben we een compilatietypecontrole die voorkomt ClassCastExceptions en maakt gieten overbodig.

Het andere voordeel is dat codeduplicatie wordt voorkomen. Zonder generieke geneesmiddelen moeten we dezelfde code kopiëren en plakken, maar voor verschillende typen. Met generieke geneesmiddelen hoeven we dit niet te doen. We kunnen zelfs algoritmen implementeren die van toepassing zijn op generieke typen.

Q3. Wat is het wissen van typen?

Het is belangrijk om te beseffen dat generieke type-informatie alleen beschikbaar is voor de compiler, niet voor de JVM. Met andere woorden, type wissen betekent dat generieke type-informatie niet beschikbaar is voor de JVM tijdens runtime, alleen compileertijd.

De redenering achter een grote implementatiekeuze is simpel: achterwaartse compatibiliteit met oudere versies van Java behouden. Wanneer een generieke code in bytecode wordt gecompileerd, zal het zijn alsof het generieke type nooit heeft bestaan. Dit betekent dat de compilatie:

  1. Vervang generieke typen door objecten
  2. Vervang begrensde typen (hierover meer in een latere vraag) door de eerste gebonden klasse
  3. Voeg het equivalent van casts in bij het ophalen van generieke objecten.

Het is belangrijk om het wissen van typen te begrijpen. Anders kan een ontwikkelaar in de war raken en denken dat hij het type tijdens runtime kan krijgen:

openbare foo (consument consument) {Type type = consumer.getGenericTypeParameter ()}

Het bovenstaande voorbeeld is een pseudo-code-equivalent van hoe dingen eruit zouden kunnen zien zonder het wissen van het type, maar helaas is het onmogelijk. Nog eens, de algemene type-informatie is niet beschikbaar tijdens runtime.

V4. Als een algemeen type wordt weggelaten bij het instantiëren van een object, wordt de code dan nog steeds gecompileerd?

Aangezien generieke geneesmiddelen niet bestonden vóór Java 5, is het mogelijk om ze helemaal niet te gebruiken. Generieke geneesmiddelen werden bijvoorbeeld achteraf toegevoegd aan de meeste standaard Java-klassen, zoals verzamelingen. Als we onze lijst vanaf vraag één bekijken, zullen we zien dat we al een voorbeeld hebben van het weglaten van het generieke type:

Lijstlijst = nieuwe ArrayList ();

Ondanks dat u kunt compileren, is het nog steeds waarschijnlijk dat er een waarschuwing van de compiler komt. Dit komt omdat we de extra controle tijdens het compileren verliezen die we krijgen door het gebruik van generieke geneesmiddelen.

Het punt om te onthouden is dat hoewel achterwaartse compatibiliteit en het wissen van typen het mogelijk maken om generieke typen weg te laten, is dit een slechte gewoonte.

V5. Hoe verschilt een generieke methode van een generiek type?

Een generieke methode is waar een typeparameter wordt geïntroduceerd in een methode,leven binnen de reikwijdte van die methode. Laten we dit proberen met een voorbeeld:

openbare statische T returnType (T-argument) {retourargument; }

We hebben een statische methode gebruikt, maar we hadden ook een niet-statische methode kunnen gebruiken als we dat hadden gewild. Door gebruik te maken van type-inferentie (behandeld in de volgende vraag), kunnen we dit aanroepen zoals elke gewone methode, zonder dat we daarbij type-argumenten hoeven op te geven.

V6. Wat is type-inferentie?

Type-inferentie is wanneer de compiler naar het type methode-argument kan kijken om een ​​generiek type af te leiden. Als we bijvoorbeeld zijn overgegaan T naar een methode die terugkeert T, dan kan de compiler het retourtype achterhalen. Laten we dit uitproberen door onze generieke methode uit de vorige vraag te gebruiken:

Geheel getal inferredInteger = returnType (1); String inferredString = returnType ("String");

Zoals we kunnen zien, is er geen cast nodig en hoeft u geen generiek type-argument door te geven. Het argumenttype leidt alleen het retourtype af.

V7. Wat is een begrensde typeparameter?

Tot dusverre hebben al onze vragen betrekking op generieke argumenten die onbegrensd zijn. Dit betekent dat onze generieke type-argumenten elk type kunnen zijn dat we willen.

Wanneer we begrensde parameters gebruiken, beperken we de typen die kunnen worden gebruikt als generieke type-argumenten.

Laten we als voorbeeld zeggen dat we ons generieke type willen dwingen altijd een subklasse van dieren te zijn:

openbare abstracte klasse Cage {abstract void addAnimal (T animal)}

Door extends te gebruiken, wij forceren T om een ​​subklasse van dieren te zijn. We zouden dan een kattenkooi kunnen hebben:

Cage catCage;

Maar we zouden geen kooi met objecten kunnen hebben, omdat een object geen subklasse van een dier is:

Kooi-objectCage; // Compilatiefout

Een voordeel hiervan is dat alle methoden van dier beschikbaar zijn voor de samensteller. We weten dat ons type het uitbreidt, dus we zouden een generiek algoritme kunnen schrijven dat op elk dier werkt. Dit betekent dat we onze methode niet hoeven te reproduceren voor verschillende dierensubklassen:

openbare leegte firstAnimalJump () {T dier = dieren.get (0); dier.jump (); }

V8. Is het mogelijk om een ​​meervoudig begrensde typeparameter te declareren?

Meerdere grenzen aangeven voor onze generieke typen is mogelijk. In ons vorige voorbeeld hebben we een enkele grens gespecificeerd, maar we kunnen ook meer specificeren als we dat willen:

openbare abstracte klasse Cage

In ons voorbeeld is het dier een klasse en vergelijkbaar is een interface. Nu moet ons type beide bovengrenzen respecteren. Als ons type een subklasse van dieren zou zijn, maar geen vergelijkbaar zou implementeren, dan zou de code niet compileren. Het is ook de moeite waard eraan te denken dat als een van de bovengrenzen een klasse is, dit het eerste argument moet zijn.

V9. Wat is een jokerteken?

Een jokerteken staat voor een onbekende type. Het wordt als volgt met een vraagteken tot ontploffing gebracht:

openbare statische leegte consumeListOfWildcardType (lijstlijst)

Hier specificeren we een lijst die van elk kan zijn type. We zouden een lijst van alles in deze methode kunnen doorgeven.

V10. Wat is een bovengrens jokerteken?

Een jokerteken met bovengrens is wanneer een jokertype erft van een concreet type. Dit is vooral handig bij het werken met verzamelingen en overerving.

Laten we proberen dit te demonstreren met een boerderijklasse die dieren zal opslaan, eerst zonder het jokerteken:

openbare klasse Boerderij {privé Lijst dieren; openbare leegte addAnimals (Collectie newAnimals) {animals.addAll (newAnimals); }}

Als we meerdere subklassen dieren hadden, zoals kat en hond, we zouden de verkeerde veronderstelling kunnen maken dat we ze allemaal aan onze boerderij kunnen toevoegen:

farm.addAnimals (katten); // Compilatiefout farm.addAnimals (honden); // Compilatiefout

Dit komt omdat de samensteller een verzameling van het betontype dier verwacht, niet een van de subklassen.

Laten we nu een jokerteken met bovengrens introduceren in onze methode dieren toevoegen:

public void addAnimals (Collectie newAnimals)

Als we het nu opnieuw proberen, wordt onze code gecompileerd. Dit komt omdat we de compiler nu vertellen om een ​​verzameling van elk subtype dier te accepteren.

V11. Wat is een onbegrensde jokerteken?

Een onbegrensd jokerteken is een jokerteken zonder boven- of ondergrens, dat elk type kan vertegenwoordigen.

Het is ook belangrijk om te weten dat het type jokerteken niet synoniem is aan object. Dit komt doordat een jokerteken elk type kan zijn, terwijl een objecttype specifiek een object is (en geen subklasse van een object kan zijn). Laten we dit demonstreren met een voorbeeld:

List wildcardList = nieuwe ArrayList (); Lijst objectList = nieuwe ArrayList (); // Compilatiefout

Nogmaals, de reden dat de tweede regel niet compileert, is dat een lijst met objecten vereist is, niet een lijst met strings. De eerste regel compileert omdat een lijst van elk onbekend type acceptabel is.

V12. Wat is een ondergrens jokerteken?

Een jokerteken met ondergrens is wanneer we in plaats van een bovengrens een ondergrens opgeven door de super trefwoord. Met andere woorden, een ondergrens jokerteken betekent dat we het type dwingen een superklasse te zijn van ons begrensde type. Laten we dit proberen met een voorbeeld:

openbare statische ongeldige addDogs (lijstlijst) {list.add (nieuwe hond ("tom"))}

Door het gebruiken van super, we zouden addDogs kunnen aanroepen op een lijst met objecten:

ArrayList-objecten = nieuwe ArrayList (); addDogs (objecten);

Dit is logisch, want een object is een superklasse van dieren. Als we het jokerteken met ondergrens niet zouden gebruiken, zou de code niet compileren, aangezien een lijst met objecten geen lijst met dieren is.

Als we erover nadenken, zouden we geen hond kunnen toevoegen aan een lijst van een subklasse van dieren, zoals katten of zelfs honden. Slechts een superklasse van dieren. Dit zou bijvoorbeeld niet compileren:

ArrayList-objecten = nieuwe ArrayList (); addDogs (objecten);

V13. Wanneer zou u ervoor kiezen om een ​​type met ondergrens te gebruiken in plaats van een type met een bovengrens?

Bij het omgaan met verzamelingen is PECS een algemene regel voor het selecteren tussen jokertekens met een boven- of ondergrens. PECS staat voor producent breidt uit, consument super.

Dit kan eenvoudig worden aangetoond door het gebruik van enkele standaard Java-interfaces en -klassen.

Producent breidt uit betekent alleen dat als u een producer van een generiek type maakt, u de strekt zich uit trefwoord. Laten we proberen dit principe toe te passen op een verzameling, om te zien waarom het logisch is:

public static void makeLotsOfNoise (Lijst dieren) {animals.forEach (Animal :: makeNoise); }

Hier willen we bellen herrie maken() op elk dier in onze collectie. Dit betekent dat onze collectie een producent is, want alles wat we ermee doen, is ervoor zorgen dat het dieren terugbrengt zodat we onze operatie kunnen uitvoeren. Als we er vanaf zouden komen strekt zich uit, zouden we niet kunnen passeren in lijsten met katten, honden of andere subklassen van dieren. Door toepassing van het producer-extends-principe hebben we de grootst mogelijke flexibiliteit.

Consument super betekent het tegenovergestelde van producent strekt zich uit. Het betekent alleen dat als we te maken hebben met iets dat elementen verbruikt, we de super trefwoord. We kunnen dit aantonen door ons vorige voorbeeld te herhalen:

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

We voegen alleen maar iets toe aan onze lijst met dieren, dus onze lijst met dieren is een consument. Daarom gebruiken we de super trefwoord. Het betekent dat we kunnen passeren in een lijst van elke superklasse van dieren, maar niet in een subklasse. Als we bijvoorbeeld zouden proberen een lijst met honden of katten door te geven, zou de code niet compileren.

Het laatste dat u moet overwegen, is wat u moet doen als een collectie zowel een consument als een producent is. Een voorbeeld hiervan kan een verzameling zijn waarin elementen zowel worden toegevoegd als verwijderd. In dit geval moet een onbegrensd jokerteken worden gebruikt.

V14. Zijn er situaties waarin algemene typegegevens tijdens runtime beschikbaar zijn?

Er is een situatie waarin een generiek type beschikbaar is tijdens runtime. Dit is wanneer een generiek type als volgt deel uitmaakt van de klassensignatuur:

openbare klasse CatCage implementeert Cage

Door reflectie te gebruiken, krijgen we deze typeparameter:

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

Deze code is enigszins broos. Het is bijvoorbeeld afhankelijk van de parameter type die wordt gedefinieerd in de directe superklasse. Maar het toont aan dat de JVM dit type informatie heeft.

De volgende » Vragen over Java Flow Control-sollicitatiegesprekken (+ antwoorden) « Voorgaand Geheugenbeheer in Java sollicitatievragen (+ antwoorden)