Pitanja o intervjuu za Java Generics (+ odgovori)

Ovaj je članak dio serije: • Pitanja za intervju za Java Collections

• Pitanja o intervjuu za sustav tipa Java

• Pitanja za intervju s Java-om (+ odgovori)

• Struktura Java razreda i pitanja za intervju za inicijalizaciju

• Java 8 pitanja za intervju (+ odgovori)

• Upravljanje memorijom u Java intervjuu Pitanja (+ odgovori)

• Pitanja o intervjuu za Java Generics (+ odgovori) (trenutni članak) • Pitanja o intervjuu za Java Flow Control (+ odgovori)

• Pitanja o intervjuu za iznimke Java (+ odgovori)

• Pitanja za intervju s Java Annotations (+ odgovori)

• Najpopularnija pitanja za proljetni okvirni intervju

1. Uvod

U ovom ćemo članku proći kroz neke primjere pitanja i odgovora za generički intervju za Javu.

Generički su jezgri koncept u Javi, prvi put predstavljeni u Javi 5. Zbog toga će ih gotovo sve Java kodne baze iskoristiti, gotovo jamčeći da će ih programer u jednom trenutku naletjeti. Zbog toga je neophodno pravilno ih razumjeti i zato je više nego vjerojatno da će ih se pitati tijekom postupka razgovora.

2. Pitanja

Q1. Što je parametar generičkog tipa?

Tip je ime a razred ili sučelje. Kao što implicira ime, parametar generičkog tipa je kada a tip može se koristiti kao parametar u deklaraciji klase, metode ili sučelja.

Počnimo s jednostavnim primjerom, onim bez generičkih lijekova, kako bismo to demonstrirali:

javno sučelje Potrošač {javna praznina troši (parametar String)}

U ovom slučaju, tip parametra metode za konzumirati () metoda je Niz. Nije parametriziran i nije podesiv.

Zamijenimo sada naše Niz tip s generičkim tipom koji ćemo nazvati T. Nazvan je ovako po dogovoru:

javno sučelje Potrošač {javna praznina troši (parametar T)}

Kada implementiramo potrošača, možemo pružiti tip da želimo da to konzumira kao argument. Ovo je generički parametar tipa:

javna klasa IntegerConsumer implementira Consumer {javna praznina troši (parametar Integer)}

U ovom slučaju, sada možemo trošiti cijele brojeve. Možemo ovo zamijeniti tip za sve što tražimo.

Q2. Koje su neke prednosti upotrebe generičkih vrsta?

Jedna od prednosti korištenja generičkih lijekova je izbjegavanje bacanja i pružanje sigurnosti tipa. To je osobito korisno u radu s kolekcijama. Pokažimo ovo:

Lista popisa = novi ArrayList (); list.add ("foo"); Objekt o = list.get (0); Niz foo = (niz) o;

U našem primjeru, tip elementa s našeg popisa nepoznat je prevoditelju. To znači da se jedino što može zajamčiti jest da se radi o objektu. Dakle, kada dohvatimo naš element, Objekt je ono što vraćamo. Kao autori koda, znamo da je to Niz, ali svoj objekt moramo preusmjeriti na nekoga da bismo problem eksplicitno riješili. To stvara puno buke i ploče.

Dalje, ako počnemo razmišljati o sobi za ručne pogreške, problem lijevanja se pogoršava. Što ako smo slučajno imali Cijeli broj na našem popisu?

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

U ovom bismo slučaju dobili a ClassCastException u vrijeme izvođenja, kao Cijeli broj ne može se baciti na Niz.

Pokušajmo se ponoviti, ovaj put koristeći generičke lijekove:

Lista popisa = novi ArrayList (); list.add ("foo"); Niz o = list.get (0); // Nema cast Integer foo = list.get (0); // Pogreška kompilacije

Kao što vidimo, pomoću generičkih sredstava imamo provjeru tipa kompajliranja koja sprečava ClassCastExceptions i uklanja potrebu za lijevanjem.

Druga prednost je izbjegavanje dupliciranja koda. Bez generičkih lijekova moramo kopirati i zalijepiti isti kod, ali za različite vrste. S generičkim lijekovima to ne moramo činiti. Možemo čak implementirati algoritme koji se primjenjuju na generičke tipove.

Q3. Što je brisanje tipa?

Važno je shvatiti da su informacije o generičkom tipu dostupne samo prevoditelju, a ne JVM-u. Drugim riječima, brisanje tipa znači da generičke informacije o tipu nisu dostupne JVM-u tijekom izvođenja, samo vrijeme kompajliranja.

Razlozi za glavni odabir implementacije su jednostavni - očuvanje povratne kompatibilnosti sa starijim verzijama Jave. Kad se generički kod kompajlira u bajt kod, bit će to kao da generički tip nikada nije postojao. To znači da će kompilacija:

  1. Zamijenite generičke tipove objektima
  2. Zamijenite ograničene tipove (o tome više u kasnijem pitanju) prvom vezanom klasom
  3. Prilikom dohvaćanja generičkih objekata umetnite ekvivalent odljevaka.

Važno je razumjeti brisanje tipa. U suprotnom, programer bi se mogao zbuniti i pomisliti da će moći dobiti tip tijekom izvođenja:

public foo (potrošački potrošač) {Type type = consumer.getGenericTypeParameter ()}

Gornji je primjer pseudo kod ekvivalent onome kako bi stvari mogle izgledati bez brisanja tipa, ali nažalost, to je nemoguće. Ponovno, informacije o generičkom tipu nisu dostupne tijekom izvođenja.

Q4. Ako je općeniti tip izostavljen pri instantaciji objekta, hoće li se kod i dalje sastaviti?

Kako generički lijekovi nisu postojali prije Jave 5, moguće ih je uopće ne koristiti. Na primjer, generički lijekovi nadograđeni su na većinu standardnih Java klasa, poput zbirki. Ako pogledamo naš popis iz prvog pitanja, vidjet ćemo da već imamo primjer izostavljanja generičkog tipa:

Lista popisa = novi ArrayList ();

Unatoč mogućnosti kompajliranja, još uvijek je vjerojatno da će upozoriti kompajler. To je zato što gubimo dodatnu provjeru vremena kompajliranja koju dobivamo korištenjem generičkih lijekova.

Važno je upamtiti to dok povratna kompatibilnost i brisanje tipova omogućuju izostavljanje generičkih tipova, to je loša praksa.

P5. Kako se generička metoda razlikuje od generičkog tipa?

Generička metoda je mjesto gdje se parametar tipa uvodi u metodu,koji žive u dosegu te metode. Pokušajmo ovo na primjeru:

javni statički T returnType (T argument) {argument povrata; }

Koristili smo statičku metodu, ali mogli smo koristiti i nestatičnu, ako smo željeli. Koristeći zaključivanje tipa (pokriveno u sljedećem pitanju), možemo se na to pozivati ​​kao na bilo koju uobičajenu metodu, bez potrebe za specificiranjem argumenata tipa kada to učinimo.

P6. Što je zaključivanje tipa?

Zaključivanje tipa je kada kompajler može pogledati vrstu argumenta metode da bi zaključio generički tip. Na primjer, ako smo prošli T na metodu koja se vraća T, tada kompajler može shvatiti tip povratka. Isprobajmo ovo pozivajući se na našu generičku metodu iz prethodnog pitanja:

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

Kao što vidimo, nema potrebe za castom, niti je potrebno prosljeđivati ​​bilo koji argument generičkog tipa. Tip argumenta izvodi samo povratni tip.

P7. Što je parametar ograničenog tipa?

Do sada su sva naša pitanja pokrivala argumente generičkih tipova koji su neograničeni. To znači da bi naši generički argumenti tipa mogli biti bilo koji tip koji želimo.

Kada koristimo ograničene parametre, ograničavamo tipove koji se mogu koristiti kao argumenti generičkog tipa.

Kao primjer, recimo da želimo prisiliti naš generički tip da uvijek bude podrazred životinja:

javni apstraktni razred Cage {apstraktno void addAnimal (T životinja)}

Korištenjem extends, forsiramo T biti podrazred životinja. Tada bismo mogli imati kavez s mačkama:

Cage catCage;

Ali nismo mogli imati kavez s predmetima, jer objekt nije podrazred životinje:

Cage objectCage; // Pogreška kompilacije

Jedna od prednosti toga je što su sastavljaču dostupne sve metode životinja. Znamo da ga naš tip proširuje, pa bismo mogli napisati generički algoritam koji djeluje na bilo koju životinju. To znači da ne moramo reproducirati našu metodu za različite podrazrede životinja:

javna praznina firstAnimalJump () {T životinja = životinje.get (0); animal.jump (); }

P8. Je li moguće deklarirati višestruko ograničeni tip parametra?

Moguće je proglasiti više granica za naše generičke tipove. U našem prethodnom primjeru naveli smo jednu vezu, ali mogli bismo navesti i više ako želimo:

javni apstraktni razred Cage

U našem primjeru životinja je klasa, a usporediva je sučelje. Naš tip mora poštovati obje gornje granice. Da je naš tip podrazred životinja, ali ne primjenjuje usporedivo, tada se kod ne bi sastavio. Također je vrijedno zapamtiti da ako je jedna od gornjih granica klasa, to mora biti prvi argument.

P9. Što je zamjenski tip?

Zamjenski tip predstavlja nepoznanicu tip. Detonira se upitnikom na sljedeći način:

javna statička praznina consumeListOfWildcardType (popis popisa)

Ovdje navodimo popis koji može biti bilo koji tip. U ovu bismo metodu mogli proslijediti popis bilo čega.

Q10. Što je gornje ograničen zamjenski znak?

Gornji ograničeni zamjenski znak je kada se zamjenski tip nasljeđuje od konkretnog tipa. To je osobito korisno u radu s zbirkama i nasljeđivanjem.

Pokušajmo to demonstrirati s farmom klase koja će čuvati životinje, prvo bez zamjenskog tipa:

gospodarstvo javne klase {privatni popis životinja; javna void addAnimals (Zbirka newAnimals) {životinje.addAll (newAnimals); }}

Kad bismo imali više podrazreda životinja, kao što su mačka i pas, mogli bismo pretpostaviti da ih možemo dodati na našu farmu:

farm.addŽivotinje (mačke); // pogreška kompilacije farm.addAnimals (psi); // Pogreška kompilacije

To je zato što sastavljač očekuje kolekciju životinje konkretnog tipa, ni jedan to podrazred.

Sada, uvedimo gornji ograničeni zamjenski znak u našu metodu dodavanja životinja:

public void addAnimals (Kolekcija newAnimals)

Ako pokušamo ponovo, naš će se kod kompajlirati. To je zato što sada govorimo sastavljaču da prihvati kolekciju bilo koje podvrste životinja.

Q11. Što je neograničeni zamjenski znak?

Neograničeni zamjenski znak je zamjenski znak bez gornje ili donje granice, koji može predstavljati bilo koju vrstu.

Također je važno znati da zamjenski tip nije sinonim za objekt. To je zato što zamjenski znak može biti bilo kojeg tipa, dok je tip objekta posebno objekt (i ne može biti podrazred objekta). Pokažimo to na primjeru:

Popis wildcardList = novi ArrayList (); Popis objectList = novi ArrayList (); // Pogreška kompilacije

Opet, razlog zbog kojeg se drugi redak ne kompilira je taj što je potreban popis objekata, a ne popis nizova. Prvi se redak sastavlja jer je prihvatljiv popis bilo koje nepoznate vrste.

Q12. Što je donje ograničen zamjenski znak?

Donji ograničeni zamjenski znak je kada, umjesto da pružimo gornju granicu, pružimo donju granicu pomoću super ključna riječ. Drugim riječima, donji ograničeni zamjenski znak znači da prisiljavamo tip da bude superklasa našeg ograničenog tipa. Pokušajmo ovo na primjeru:

javna statička void addDogs (popis popisa) {list.add (novi Pas ("tom"))}

Pomoću super, mogli bismo nazvati addDogs na popisu objekata:

ArrayList objekti = novi ArrayList (); addDogs (objekti);

To ima smisla, jer je objekt superklasa životinja. Da ne koristimo donji ograničeni zamjenski znak, kod se ne bi sastavio, jer popis objekata nije popis životinja.

Ako razmislimo o tome, ne bismo mogli dodati psa na popis bilo koje podrazrede životinja, poput mačaka ili čak pasa. Samo superrazred životinja. Na primjer, ovo se ne bi kompajliralo:

ArrayList objekti = novi ArrayList (); addDogs (objekti);

Q13. Kada biste odlučili koristiti tip s donjim ograničenjem u odnosu na tip s gornjim ograničenjem?

Kada se radi o zbirkama, uobičajeno pravilo za odabir između gornjih ili donjih ograničenih zamjenskih znakova je PECS. PECS je kratica proizvođač produžuje, potrošač super.

To se lako može pokazati upotrebom nekih standardnih Java sučelja i klasa.

Producent produžuje samo znači da ako stvarate proizvođača generičkog tipa, tada upotrijebite proteže se ključna riječ. Pokušajmo primijeniti ovo načelo na zbirku kako bismo vidjeli zašto to ima smisla:

javna statička praznina makeLotsOfNoise (Popis životinja) {životinje.zaEach (Animal :: makeNoise); }

Evo, želimo nazvati praviti buku() na svaku životinju u našoj kolekciji. To znači da je naša kolekcija proizvođač, kao što sve što radimo s tim postižemo da vratimo životinje na koje ćemo obaviti operaciju. Kad bismo se riješili proteže se, ne bismo mogli proći na popisima mačaka, psi ili bilo koje druge podrazrede životinja. Primjenom principa proizvođač proširuje, imamo najveću moguću fleksibilnost.

Potrošačka super znači suprotno od produži produžitelj. Sve to znači da ako imamo posla s nečim što troši elemente, onda bismo trebali koristiti super ključna riječ. To možemo pokazati ponavljanjem našeg prethodnog primjera:

javna statička void addCats (Popis životinja) {životinje.add (novi Cat ()); }

Naš popis životinja samo dodajemo, tako da je naš popis životinja potrošač. Zbog toga koristimo super ključna riječ. To znači da bismo mogli uvrstiti popis bilo koje superrazrede životinja, ali ne i potklase. Na primjer, ako bismo pokušali dodati popis pasa ili mačaka, tada se kod ne bi sastavio.

Konačno je razmotriti što učiniti ako je kolekcija i potrošač i proizvođač. Primjer za to može biti zbirka u kojoj se dodaju i uklanjaju elementi. U tom slučaju treba koristiti neograničeni zamjenski znak.

P14. Postoje li situacije u kojima su generičke informacije dostupne tijekom izvođenja?

Postoji jedna situacija u kojoj je generički tip dostupan tijekom izvođenja. To je kada je generički tip dio potpisa klase i to ovako:

javni razred CatCage implementira Cage

Korištenjem refleksije dobivamo ovaj parametar tipa:

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

Ovaj je kod donekle lomljiv. Na primjer, ovisi o parametru tipa koji je definiran na neposrednoj superklasi. Ali, to pokazuje da JVM ima takve informacije.

Sljedeći » Pitanja za intervju s Java Flow Control (+ odgovori) « Prethodna pitanja o upravljanju memorijom u Javi (+ odgovori)