Java Generics intervjuspørsmål (+ svar)

Denne artikkelen er en del av en serie: • Java Collections Interview Questions

• Java Type System Interview Questions

• Java-samtalespørsmål om samtidighet (+ svar)

• Spørsmål om Java-klassestruktur og initialisering

• Java 8 intervjuspørsmål (+ svar)

• Minnehåndtering i Java-intervjuspørsmål (+ svar)

• Java Generics-intervjuspørsmål (+ svar) (nåværende artikkel) • Java Flow Control-intervjuspørsmål (+ svar)

• Java-unntaksspørsmål (+ svar)

• Spørsmål om Java-merknader (+ svar)

• Spørsmål om topp vårrammeverk

1. Introduksjon

I denne artikkelen vil vi gå gjennom noen eksempler på spørsmål og svar på Java generiske intervju.

Generikk er et kjernekonsept i Java, først introdusert i Java 5. På grunn av dette vil nesten alle Java-kodebaser bruke dem, noe som nesten garanterer at en utvikler vil støte på dem på et eller annet tidspunkt. Det er derfor det er viktig å forstå dem riktig, og det er derfor mer enn sannsynlig at de blir spurt om under en intervjuprosess.

2. Spørsmål

Q1. Hva er en generisk type parameter?

Type er navnet på en klasse eller grensesnitt. Som antydet av navnet, en generisk type parameter er når en type kan brukes som parameter i en klasse, metode eller grensesnitterklæring.

La oss starte med et enkelt eksempel, et uten generiske legemidler, for å demonstrere dette:

offentlig grensesnitt Forbruker {public void consume (String parameter)}

I dette tilfellet vil metodeparametertypen til forbruke() metoden er String. Den er ikke parametrisert og ikke konfigurerbar.

La oss nå erstatte vår String skriv med en generisk type som vi vil kalle T. Den heter slik etter konvensjonen:

offentlig grensesnitt Forbruker {offentlig tomrom forbruker (T-parameter)}

Når vi implementerer forbrukeren vår, kan vi tilby type at vi vil at den skal konsumere som et argument. Dette er en generisk parameterparameter:

offentlig klasse IntegerConsumer implementerer forbruker {public void consume (Integer parameter)}

I dette tilfellet kan vi nå konsumere heltall. Vi kan bytte ut dette type for hva vi trenger.

Q2. Hva er noen fordeler med å bruke generiske typer?

En fordel med å bruke generiske legemidler er å unngå avstøpninger og gi typesikkerhet. Dette er spesielt nyttig når du arbeider med samlinger. La oss demonstrere dette:

Listeliste = ny ArrayList (); list.add ("foo"); Objekt o = list.get (0); String foo = (String) o;

I vårt eksempel er elementtypen i listen ukjent for kompilatoren. Dette betyr at det eneste som kan garanteres er at det er et objekt. Så når vi henter elementet vårt, an Gjenstand er det vi får tilbake. Som forfatterne av koden vet vi at det er en Streng, men vi må kaste vårt objekt til en for å løse problemet eksplisitt. Dette gir mye støy og kjeleplate.

Neste, hvis vi begynner å tenke på rommet for manuell feil, blir castingproblemet verre. Hva om vi ved et uhell hadde en Heltall i listen vår?

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

I dette tilfellet vil vi få en ClassCastException ved kjøretid, som en Heltall kan ikke kastes til String.

La oss prøve å gjenta oss selv, denne gangen ved hjelp av generiske legemidler:

Listeliste = ny ArrayList (); list.add ("foo"); Streng o = list.get (0); // Ingen rollebesetning Heltall foo = list.get (0); // Kompileringsfeil

Som vi kan se, ved å bruke generiske stoffer har vi en kompileringstypekontroll som forhindrer ClassCastExceptions og fjerner behovet for støping.

Den andre fordelen er å unngå duplisering av kode. Uten generikk må vi kopiere og lime inn den samme koden, men for forskjellige typer. Med generiske stoffer trenger vi ikke gjøre dette. Vi kan til og med implementere algoritmer som gjelder generiske typer.

Q3. Hva er type sletting?

Det er viktig å innse at generisk typeinformasjon bare er tilgjengelig for kompilatoren, ikke JVM. Med andre ord, type sletting betyr at generisk typeinformasjon ikke er tilgjengelig for JVM på kjøretid, bare kompileringstid.

Begrunnelsen bak det store implementeringsvalget er enkel - å bevare bakoverkompatibilitet med eldre versjoner av Java. Når en generisk kode blir kompilert i bytekode, vil det være som om den generiske typen aldri eksisterte. Dette betyr at samlingen vil:

  1. Erstatt generiske typer med gjenstander
  2. Erstatt avgrensede typer (Mer om disse i et senere spørsmål) med den første avgrensede klassen
  3. Sett inn ekvivalenten av kaster når du henter generiske gjenstander.

Det er viktig å forstå type sletting. Ellers kan en utvikler bli forvirret og tro at de kan få typen ved kjøretid:

public foo (Consumer consumer) {Type type = consumer.getGenericTypeParameter ()}

Ovennevnte eksempel er en pseudokode ekvivalent med hvordan ting kan se ut uten sletting av typen, men dessverre er det umulig. Igjen, informasjonen om generisk type er ikke tilgjengelig på kjøretid.

Q4. Hvis en generisk type er utelatt når du installerer et objekt, vil koden fortsatt kompileres?

Siden generikk ikke eksisterte før Java 5, er det mulig å ikke bruke dem i det hele tatt. For eksempel ble generikk ettermontert til de fleste vanlige Java-klasser som samlinger. Hvis vi ser på listen vår fra spørsmål 1, vil vi se at vi allerede har et eksempel på å utelate den generiske typen:

Listeliste = ny ArrayList ();

Til tross for å kunne kompilere, er det fortsatt sannsynlig at det kommer en advarsel fra kompilatoren. Dette er fordi vi mister den ekstra sjekken for kompileringstid som vi får ved å bruke generiske legemidler.

Poenget å huske er at mens bakoverkompatibilitet og type sletting gjør det mulig å utelate generiske typer, er det dårlig praksis.

Q5. Hvordan skiller en generisk metode seg fra en generisk type?

En generisk metode er hvor en typeparameter blir introdusert til en metode,lever innenfor rammen av denne metoden. La oss prøve dette med et eksempel:

offentlig statisk T returnType (T argument) {retur argument; }

Vi har brukt en statisk metode, men kunne også brukt en ikke-statisk hvis vi ønsket det. Ved å utnytte typeinferanse (dekket i neste spørsmål), kan vi påberope dette som enhver vanlig metode, uten å måtte spesifisere noen type argumenter når vi gjør det.

Q6. Hva er typeinferanse?

Type inferens er når kompilatoren kan se på typen av et metodargument for å utlede en generisk type. For eksempel hvis vi passerte inn T til en metode som returnerer T, da kan kompilatoren finne ut returtypen. La oss prøve dette ved å påkalle vår generiske metode fra forrige spørsmål:

Integer inferredInteger = returnType (1); String inferredString = returnType ("String");

Som vi kan se, er det ikke behov for en rollebesetning, og det er ikke nødvendig å sende inn noen generisk type argument. Argumenttypen utleder bare returtypen.

Q7. Hva er en begrenset type parameter?

Så langt har alle spørsmålene våre dekket generiske typer argumenter som er ubegrensede. Dette betyr at våre generiske type argumenter kan være hvilken type vi ønsker.

Når vi bruker avgrensede parametere, begrenser vi typene som kan brukes som generiske typeargumenter.

La oss si et eksempel, vi vil tvinge vår generiske type til alltid å være en underklasse av dyr:

offentlig abstrakt klasse Cage {abstrakt ugyldig addAnimal (T dyr)}

Ved å bruke utvidelser, vi tvinger T å være en underklasse av dyr. Vi kunne da ha et bur med katter:

BurkattBur;

Men vi kunne ikke ha et bur med gjenstander, ettersom et objekt ikke er en underklasse av et dyr:

Bur objektBur; // Kompileringsfeil

En fordel med dette er at alle metodene for dyr er tilgjengelige for kompilatoren. Vi vet at vår type utvider den, så vi kan skrive en generisk algoritme som fungerer på alle dyr. Dette betyr at vi ikke trenger å reprodusere metoden vår for forskjellige dyrekategorier:

offentlig ugyldig firstAnimalJump () {T animal = animals.get (0); animal.jump (); }

Q8. Er det mulig å erklære en parameter med flere avgrensede typer?

Det er mulig å erklære flere grenser for våre generiske typer. I vårt forrige eksempel spesifiserte vi en enkelt grense, men vi kunne også spesifisere mer hvis vi ønsker:

offentlig abstrakt klasse Cage

I vårt eksempel er dyret en klasse, og sammenlignbart er et grensesnitt. Nå må vår type respektere begge disse øvre grensene. Hvis vår type var en underklasse av dyr, men ikke implementerte sammenlignbare, ville ikke koden kompilere. Det er også verdt å huske at hvis en av de øvre grensene er en klasse, må det være det første argumentet.

Q9. Hva er en jokertegnetype?

En jokertegn representerer en ukjent type. Det er detonert med et spørsmålstegn som følger:

offentlig statisk tomrom consumeListOfWildcardType (liste liste)

Her spesifiserer vi en liste som kan være hvilken som helst type. Vi kan sende en liste over hva som helst til denne metoden.

Q10. Hva er et øvre avgrenset jokertegn?

Et jokertegn med øvre grenser er når en jokertegnetype arver fra en konkret type. Dette er spesielt nyttig når du arbeider med samlinger og arv.

La oss prøve å demonstrere dette med en gårdsklasse som vil lagre dyr, først uten wildcard-typen:

offentlig klasse Farm {private Listedyr; public void addAnimals (Collection newAnimals) {animals.addAll (newAnimals); }}

Hvis vi hadde flere underklasser av dyr, som katt og hund, vi kan anta feil antagelse om at vi kan legge dem alle til gården vår:

farm.addDyr (katter); // Kompileringsfeil farm.addAnimals (hunder); // Kompileringsfeil

Dette er fordi kompilatoren forventer en samling av dyret av betongtypen, ikke en den underklasser.

La oss nå introdusere et øvre avgrenset jokertegn for metoden vår for å legge til dyr:

public void addAnimals (Collection newAnimals)

Nå hvis vi prøver igjen, vil koden vår kompilere. Dette er fordi vi nå ber kompilatoren om å godta en samling av alle undertyper av dyr.

Q11. Hva er et ubegrenset jokertegn?

Et ubegrenset jokertegn er et jokertegn uten øvre eller nedre grense, som kan representere hvilken som helst type.

Det er også viktig å vite at wildcard-typen ikke er synonymt med objekt. Dette er fordi et jokertegn kan være av hvilken som helst type, mens en objekttype spesifikt er et objekt (og ikke kan være en underklasse av et objekt). La oss demonstrere dette med et eksempel:

Liste wildcardList = ny ArrayList (); List objectList = new ArrayList (); // Kompileringsfeil

Igjen, årsaken til at den andre linjen ikke kompilerer, er at det kreves en liste over objekter, ikke en liste over strenger. Første linje kompileres fordi en liste av ukjent type er akseptabel.

Q12. Hva er et wildcard med lavere grenser?

Et jokertegn med nedre grense er når vi i stedet for å gi en øvre grense, gir vi en nedre grense ved å bruke super nøkkelord. Med andre ord, et jokertegn med lavere grenser betyr at vi tvinger typen til å være en superklasse av vår begrensede type. La oss prøve dette med et eksempel:

offentlig statisk tomrom addDogs (Listeliste) {list.add (ny hund ("tom"))}

Ved bruk av super, vi kan kalle addDogs på en liste over objekter:

ArrayList objekter = ny ArrayList (); addDogs (objekter);

Dette er fornuftig, ettersom et objekt er en superklasse av dyr. Hvis vi ikke brukte jokertegnet med nedre grenser, ville ikke koden kompilert, da en liste over objekter ikke er en liste over dyr.

Hvis vi tenker på det, ville vi ikke kunne legge til en hund i en liste over noen underklasse av dyr, for eksempel katter eller til og med hunder. Bare en superklasse av dyr. For eksempel vil dette ikke kompilere:

ArrayList objekter = ny ArrayList (); addDogs (objekter);

Q13. Når vil du velge å bruke en lavere avgrenset type mot en øvre avgrenset type?

Når du arbeider med samlinger, er en vanlig regel for å velge mellom jokertegn med øvre eller nedre grense PECS. PECS står for produsent utvider, forbruker super.

Dette kan enkelt demonstreres ved bruk av noen standard Java-grensesnitt og klasser.

Produsent utvider betyr bare at hvis du lager en produsent av en generisk type, så bruk strekker nøkkelord. La oss prøve å bruke dette prinsippet på en samling for å se hvorfor det er fornuftig:

offentlig statisk tomrom makeLotsOfNoise (List animals) {animals.forEach (Animal :: makeNoise); }

Her vil vi ringe lage støy() på hvert dyr i samlingen vår. Dette betyr at samlingen vår er produsent, ettersom alt vi gjør med det er å få det til å returnere dyr for oss å utføre vår operasjon på. Hvis vi ble kvitt strekker, ville vi ikke kunne sende inn lister over katter, hunder eller andre underklasser av dyr. Ved å bruke produsent utvider prinsippet, har vi størst mulig fleksibilitet.

Forbruker super betyr det motsatte av produsent utvider. Alt det betyr er at hvis vi har å gjøre med noe som forbruker elementer, så bør vi bruke super nøkkelord. Vi kan demonstrere dette ved å gjenta vårt forrige eksempel:

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

Vi legger bare til listen over dyr, så listen over dyr er en forbruker. Dette er grunnen til at vi bruker super nøkkelord. Det betyr at vi kunne passere i en liste over hvilken som helst superklasse av dyr, men ikke en underklasse. For eksempel, hvis vi prøvde å sende inn en liste over hunder eller katter, ville ikke koden kompilere.

Den siste tingen å vurdere er hva du skal gjøre hvis en samling både er forbruker og produsent. Et eksempel på dette kan være en samling der elementer både legges til og fjernes. I dette tilfellet bør et ubegrenset jokertegn brukes.

Q14. Er det noen situasjoner der generell typeinformasjon er tilgjengelig på kjøretid?

Det er en situasjon der en generisk type er tilgjengelig på kjøretid. Dette er når en generisk type er en del av klassesignaturen slik:

offentlig klasse CatCage implementerer Cage

Ved å bruke refleksjon får vi denne typen parameter:

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

Denne koden er noe sprø. For eksempel er det avhengig av at typeparameteren blir definert i den umiddelbare superklassen. Men det viser at JVM har denne typen informasjon.

Neste » Java Flow Control Intervju Spørsmål (+ Svar) « Tidligere minnehåndtering i Java-intervjuspørsmål (+ svar)

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