Osnove Java generike

1. Uvod

Java Generics predstavljeni su u JDK 5.0 s ciljem smanjenja bugova i dodavanja dodatnog sloja apstrakcije nad tipovima.

Ovaj je članak kratki uvod u Generike u Javi, cilj koji stoji iza njih i kako se mogu koristiti za poboljšanje kvalitete našeg koda.

2. Potreba za generičkim lijekovima

Zamislimo scenarij u kojem želimo stvoriti popis na Javi za pohranu Cijeli broj; možemo doći u iskušenje da napišemo:

Popis popisa = novi LinkedList (); list.add (novi cijeli broj (1)); Cijeli broj i = list.iterator (). Next (); 

Iznenađujuće, kompajler će se žaliti na posljednji redak. Ne zna koji se tip podataka vraća. Prevoditelj će zahtijevati eksplicitno lijevanje:

Integer i = (Integer) list.iterator.next ();

Ne postoji ugovor koji bi mogao jamčiti da je vrsta povrata popis Cijeli broj. Definirani popis mogao bi sadržavati bilo koji objekt. Znamo samo da popis dohvaćamo pregledavanjem konteksta. Kada se gledaju vrste, može samo jamčiti da je Objekt, stoga zahtijeva izričitu cast kako bi se osiguralo da je tip siguran.

Ova cast može biti dosadna, znamo da je vrsta podataka na ovom popisu Cijeli broj. Glumačka ekipa također pretrpava naš kod. To može uzrokovati pogreške u izvedbi povezane s vrstom ako programer pogriješi s eksplicitnim lijevanjem.

Bilo bi puno lakše kad bi programeri mogli izraziti svoju namjeru da koriste određene tipove, a kompajler može osigurati ispravnost takvog tipa. Ovo je srž ideja koja stoji iza generičkih lijekova.

Izmijenimo prvi redak prethodnog isječka koda na:

Popis popisa = novi LinkedList ();

Dodavanjem dijamantskog operatora koji sadrži tip, sužavamo specijalizaciju ovog popisa samo na Cijeli broj tj. mi određujemo vrstu koja će se nalaziti unutar popisa. Prevoditelj može primijeniti tip u vrijeme sastavljanja.

U malim programima ovo se može činiti trivijalnim dodatkom, no u većim programima to može dodati značajnu robusnost i olakšati čitanje programa.

3. Generičke metode

Generičke metode su one metode koje su napisane s jednom deklaracijom metode i mogu se pozvati s argumentima različitih vrsta. Prevoditelj će osigurati ispravnost bilo koje vrste koja se koristi. Ovo su neka svojstva generičkih metoda:

  • Generičke metode imaju parametar tipa (dijamantni operator koji zatvara tip) prije tipa povratka deklaracije metode
  • Parametri tipa mogu biti ograničeni (granice su objašnjene kasnije u članku)
  • Generičke metode mogu imati različite parametre tipa odvojene zarezima u potpisu metode
  • Tijelo metode za generičku metodu je poput uobičajene metode

Primjer definiranja generičke metode za pretvaranje niza u popis:

javni popis fromArrayToList (T [] a) {return Arrays.stream (a) .collect (Collectors.toList ()); }

U prethodnom primjeru, u potpisu metode podrazumijeva da će se metoda baviti generičkim tipom T. To je potrebno čak i ako se metoda vraća praznom.

Kao što je gore spomenuto, metoda se može nositi s više od jednog generičkog tipa, gdje je to slučaj, svi generički tipovi moraju se dodati potpisu metode, na primjer, ako gornju metodu želimo izmijeniti da bi se bavila tipom T i tip G, to bi trebalo napisati ovako:

javni statički popis fromArrayToList (T [] a, funkcija mapperFunction) {return Arrays.stream (a) .map (mapperFunction) .collect (Collectors.toList ()); }

Prolazimo funkciju koja pretvara niz s elementima tipa T popisati s elementima tipa G. Primjer bi bio pretvorba Cijeli broj na svoje Niz zastupanje:

@Test javna praznina givenArrayOfIntegers_thanListOfStringReturnedOK () {Integer [] intArray = {1, 2, 3, 4, 5}; Popis stringList = Generics.fromArrayToList (intArray, Object :: toString); assertThat (stringList, hasItems ("1", "2", "3", "4", "5")); }

Vrijedno je napomenuti da je Oracleova preporuka koristiti veliko slovo za predstavljanje generičkog tipa i odabrati opisnije slovo za predstavljanje formalnih tipova, na primjer u Java Collections T koristi se za tip, K za ključ, V za vrijednost.

3.1. Ograničeni generički lijekovi

Kao što je već spomenuto, parametri tipa mogu biti ograničeni. Ograničeno znači "ograničen“, Možemo ograničiti tipove koji se mogu prihvatiti metodom.

Na primjer, možemo odrediti da metoda prihvaća tip i sve njegove podrazrede (gornja granica) ili tip sve svoje superklase (donja granica).

Za deklaraciju gornjeg ograničenog tipa koristimo ključnu riječ proteže se nakon tipa nakon kojeg slijedi gornja granica koju želimo koristiti. Na primjer:

javni popis fromArrayToList (T [] a) {...} 

Ključna riječ proteže se ovdje se koristi da bi se značilo da je tip T proširuje gornju granicu u slučaju klase ili provodi gornju granicu u slučaju sučelja.

3.2. Višestruke veze

Tip također može imati više gornjih granica kako slijedi:

Ako je jedna od vrsta koja se proširuje za T je razred (tj Broj), mora se staviti na prvo mjesto popisa ograničenja. Inače, to će uzrokovati pogrešku u vremenu kompajliranja.

4. Korištenje zamjenskih znakova s ​​generičkim lijekovima

Zamjenski znakovi predstavljeni su upitnikom na Javi “?”, A koriste se za označavanje nepoznate vrste. Zamjenski znakovi posebno su korisni kada se koriste generički lijekovi i mogu se koristiti kao vrsta parametra, ali prvo postoji važno bilješka za razmatranje.

Poznato je da Objekt je supertip svih Java klasa, međutim, zbirka Objekt nije supertip bilo koje kolekcije.

Na primjer, a Popis nije supertip Popis i dodjeljivanje varijable tipa Popis na varijablu tipa Popis izazvat će pogrešku kompajlera. Ovo je za sprečavanje mogućih sukoba koji se mogu dogoditi ako u istu zbirku dodamo heterogene tipove.

Isto se pravilo odnosi na bilo koju zbirku tipa i njegovih podtipova. Razmotrite ovaj primjer:

javna statička praznina paintAllBuildings (Popis zgrada) {building.forEach (Building :: paint); }

ako zamislimo podvrstu Zgrada, na primjer, a Kuća, ne možemo koristiti ovu metodu s popisom Kuća, čak iako Kuća je podvrsta Zgrada. Ako ovu metodu trebamo koristiti s tipom Building i svim njezinim podtipovima, tada ograničeni zamjenski znak može učiniti čaroliju:

javna statička praznina paintAllBuildings (Popis zgrada) {...} 

Sada će ova metoda raditi s tipom Zgrada i sve njegove podvrste. To se naziva gornjim ograničenim zamjenskim znakom gdje tip Zgrada je gornja granica.

Zamjenski znakovi također se mogu odrediti s donjom granicom, pri čemu nepoznati tip mora biti nadtip navedenog tipa. Donje granice mogu se odrediti pomoću super ključna riječ nakon koje slijedi određena vrsta, na primjer, "nepoznata vrsta" koja je superklasa T (= T i svi njegovi roditelji).

5. Tip Brisanje

Generički su dodani u Javu da bi osigurali sigurnost tipova i kako generički ne bi uzrokovali režijske troškove tijekom izvođenja, kompajler primjenjuje postupak tzv. brisanje tipa o generičkim lijekovima u vrijeme sastavljanja.

Brisanje tipa uklanja sve parametre tipa i zamjenjuje ih njihovim granicama ili s Objekt ako je parametar tipa neograničen. Stoga bytecode nakon kompilacije sadrži samo normalne klase, sučelja i metode, čime se osigurava da se ne proizvode novi tipovi. Ispravno lijevanje primjenjuje se i na Objekt tip u vrijeme sastavljanja.

Ovo je primjer brisanja tipa:

javni popis genericMethod (popis popisa) {return list.stream (). collect (Collectors.toList ()); } 

S brisanjem tipa, neograničeni tip T zamjenjuje se s Objekt kako slijedi:

// za ilustraciju javni popis withErasure (popis popisa) {return list.stream (). collect (Collectors.toList ()); } // što u praksi rezultira javnim Popis withErasure (Popis popisa) {return list.stream (). collect (Collectors.toList ()); } 

Ako je tip ograničen, tada će tip biti zamijenjen vezanim u vrijeme sastavljanja:

javna void generička metoda (T t) {...} 

bi se promijenio nakon kompilacije:

javna void generička metoda (zgrada t) {...}

6. Generički i primitivni tipovi podataka

Ograničenje generičkih lijekova u Javi je da parametar type ne može biti primitivan tip.

Na primjer, sljedeće se ne kompajlira:

Lista popisa = novi ArrayList (); list.add (17);

Da bismo razumjeli zašto primitivni tipovi podataka ne rade, sjetimo se toga generički lijekovi značajka su vremena kompajliranja, što znači da se parametar tipa briše i svi generički tipovi implementiraju se kao tip Objekt.

Kao primjer, pogledajmo dodati metoda popisa:

Lista popisa = novi ArrayList (); list.add (17);

Potpis dodati metoda je:

boolean add (E e);

I bit će sastavljen u:

boolean add (Objekt e);

Stoga parametri tipa moraju biti konvertibilni u Objekt. Budući da se primitivni tipovi ne protežu Objekt, ne možemo ih koristiti kao parametre tipa.

Međutim, Java nudi tipične vrste za primitive, uz automatsko i otpakiranje kako bi ih raspakirali:

Cijeli broj a = 17; int b = a; 

Dakle, ako želimo stvoriti popis koji može sadržavati cijele brojeve, možemo koristiti omot:

Lista popisa = novi ArrayList (); list.add (17); int prvo = list.get (0); 

Sastavljeni kod bit će ekvivalent:

Lista popisa = novi ArrayList (); list.add (Integer.valueOf (17)); int prvo = ((Integer) list.get (0)). intValue (); 

Buduće verzije Jave mogu dopustiti primitivne vrste podataka za generičke. Cilj projekta Valhalla je poboljšati način na koji se postupa s generičkim lijekovima. Ideja je primijeniti generičku specijalizaciju kako je opisano u JEP 218.

7. Zaključak

Java Generics moćan je dodatak jeziku Java jer čini posao programera lakšim i manje sklonim pogreškama. Generički primjenjuju ispravnost tipa u vrijeme sastavljanja i, što je najvažnije, omogućuju implementaciju generičkih algoritama bez stvaranja dodatnih troškova za naše aplikacije.

Izvorni kod koji prati članak dostupan je na GitHubu.