U računarstvu, generičko programiranje je tehnika koja dozvoljava da jedna promenljiva može da čuva različite tipove podataka (takozvana višeobličnost ili polimorfizam) sve dok su zadovoljeni određeni uslovi kao što su podklasa i pravilna deklaracija. Dakle, dozvoljava nam stvaranje funkcija i klasa koje ne zavise od tipa. Primer: -{STL}- vektor, lista, stek itd. Na primer, ako se želi napraviti lista koristeći generičnost, moguća deklaracija bi bila -{List}- <T>, gde T predstavlja vrstu podataka. Kada se načini primerak može se napraviti -{List<Integer>}- ili -{List<Animal>}-. Prema listi se zatim postupa kao prema listi onog tipa podataka koji je naveden. Od objektno orijentisanih programskih jezika, programski jezici -{C++, D, BETA, Eiffel, Ada i neke verzije Jave}- (1.5 i novije) podržavaju generičke tipove podataka. -{VB.NET i C#}- su počeli da podržavaju generičke tipove od verzije -{.NET}- 2.0. Šabloni – osnova za generičko programiranje: šablon je u stvari formula ili recept za stvaranje klase ili funkcije. Postoje funkcijski šabloni i šabloni klase.
Generičke funkcije i klase – -{template}- Pod pojmom generičkog programiranja podrazumeva se izrada programskog koda koji se u nepromenjenom obliku može primeniti na različite tipove podataka.
U C jeziku se za generičko programiranje koriste predprocesorske makro naredbe, npr:#define Kvadrat(x) x*xOvo je makro naredba kojom se deo koda koji je u zagradama označen sa x zamenjuje sa x*x. Makro naredbe nisu funkcije. One se realizuju u toku kompajliranja leksičkom zamenom teksta.
Primer: Makro naredba:Izvršava se kao:int i; Kvadrat(i) * 3int i; i*i*3float x; x*Kvadrat(6.7)float x; x*6.7*6.7float x; x=Kvadrat(x+6.7)float x; x=x+6.7*x+6.7
//greška U prva dva primera pokazano je kako se ista makro naredba koristi za tipove -{int}- i -{float}-. Greška u trećem primeru, se može izbeći tako da se „argumenti“ makro naredbe napišu u zagradama tj.#define Kvadrat(x) (x)*(x)Problem kod makroa je da je kod makroa nepregledan i teško je uočiti -{compile}- – greške, budući da kompajler samo kod makroa nalepi umesto poziva i moguće su semantičke greške koje je vrlo teško otkriti
To je jedan od razloga zbog kojeg u C++-u imamo generičke (template) funkcije (funkcijske šablone). C++ je strogo tipizirani jezik, tj pri definiciji funkcije moramo navesti tipove parametara. -{C++}- dopušta korišćenje preopterećenih -{(overloaded)}- funkcija. To su funkcije koje imaju jednaka imena (i pripadaju istom dosegu – -{namespaceu}-) ali različitu listu parametara. Preopterećivanje funkcije ima nekoliko nedostataka: ukoliko želimo nešto promeniti u kodu funkcije moramo to učiniti na puno mesta, pa se povećava i mogućnost greške, ne možemo predvideti na kojim će sve tipovima korisnik hteti da pozove funkciju.
U C++ se isti efekat kao u C-u sa -{#define}- može postići korišćenjem definisanjem -{inline}- funkcija:inline int Kvadrat(int x) { return x*x; } inline float Kvadrat(float x) { return x*x; } inline double Kvadrat(double x) { return x*x; }Preporuka: bolje je da se koriste -{inline}- funkcije nego makro naredbe, jer se time osigurava bolja kontrola tipova i izbegava mogućnost pojave prethodno opisane greške.
Primetimo da sva tri tipa definicije funkcije Kvadrat imaju isti oblik:T Kvadrat(T x) { return x*x; }gde T može biti -{int, float}- ili -{double}-. Ova definicija ima generički oblik. U C++ jeziku se može vršiti generičko definisanje funkcija, na način da se ispred definicije ili deklaracije funkcije, pomoću ključne reči -{template}-, označi da T predstavlja „generički tip“.template <class T> T Kvadrat (T x) { return x*x; }ilitemplate <typename T> T Kvadrat(T x) { return x*x; }Razlika u ova dva zapisa je u upotrebi ključne reči -{class}- ili -{typename}-, ali značenje je potpuno isto. Izvršenje poziva funkcije se mora obaviti sa stvarnim tipovima:int x,y; y=Kvadrat<int>(x); Pogodno je da se klase i funkcije mogu pisati generički, parametrizovano tipovima podataka. Takve generičke klase i funkcije nazivaju se u jeziku C++ šablonima (templates). Iz šablona se generišu stvarne klase, odnosno funkcije, za konkretne tipove. Generički mehanizamGenerički mehanizam je u potpunosti statički -substitucija parametara je u vreme prevođenja. Funkcijski šablon se ne koristi dok kompajler ne naiđe na poziv generičke funkcije. Tek tada se stvara i prevodi nova varijanta funkcije u zavisnosti o tipu na kojem je funkcija pozvana. Taj proces stvaranja nove konkretne varijante funkcije iz funkcijskog šablona naziva se instancijacija. Tipovi se kod funkcijskih šablona prepoznaju automatski. Zbog toga se i definicija i implementacija takvih funkcija piše u .h datoteku. Kompajler tek u trenutku poziva funkcije može odrediti način kako se prevodi generička funkcija, stoga su generičke funkcije po upotrebi ekvivalentne makro naredbama. Da bi se moglo izvršiti kompajliranje programa u kojem se koristi generička funkcija, kompajleru mora biti na raspolaganju potpuna definicija funkcije, a ne samo deklaracija funkcije kao što je slučaj kod upotrebe regularnih funkcija.
Generički tip T se može koristiti unutar funkcije za oznaku tipa lokalne promenljive:template <class T> T Kvadrat(T x){ T result=x*x; return result; } int main() { int i=5,k; float a=10.9,b; k=Kvadrat<int>(i); b=Kvadrat<float>(a); cout <<k<<endl; cout<<b<<endl; return 0; }25 118.81Ne mora se uvek, pri pozivu funkcije, specificirati generički tip ako se iz tipova argumenata nedvosmisleno može zaključiti koji se parametarski tip koristi.int i=5,k; float a=10.9,b; k=Kvadrat(i); b=Kvadrat(a); cout <<k<<endl; cout<<b<<endl;Preporuka je ipak, da se uvek eksplicitno specifira parametarski tip.
Mogu se definisati generičke funkcije sa više generičkih tipova. Primer:template <class T, class U> t GetMin (T a, U b){ return (a<b?a:b); }definiše funkciju sa dva argumenata kojima se pridružuju generički tipovi T i U. Moguće je izvršiti poziv funkcije na sledeći način:
ili još jednostavnijei=GetMin (j,l);Iako su j i l različitog tipa, kompajler sam vrši konverzije integralnih tipova.
Generički klasni tipovi Pretpostavimo da radimo sa nekim skupom objekata koje želimo da nekako organizujemo. U tom slučaju koriste se klase koje se označavaju kao kolekcije (kolekcija=zbirka, skupljanje). One definišu objekte koji se koriste za organizaciju drugih objekata odgovarajućeg tipa u kolekcije na odgovarajući način. Moguće je u kolekciju dodavati objekte različitog tipa, ali onda imamo problem utvrđivanja tipa objekta prilikom njegovog korišćenja. Ako pokušamo da neki objekat iz ove kolekcije upotrebimo kao objekat neke druge klase, nailazimo na problem. Npr. neka smo omogućili da kolekciji mogu da se dodaju objekti klasa -{Tacka, Duz}- i -{String}-. Ako, primera radi, pokušamo da upotrebimo neki objekat klase -{Duz}- ili -{String}- kao objekat klase -{Tacka}-, program neće biti preveden. Da bi se izbegli ovakvi problemi, treba definisati kolekciju tako da se onemogući slučajno dodavanje objekta pogrešnog tipa. Jedan način za rešenje ovog problema je definisati zasebne kolekcije koje mogu da sadrže samo objekte jednog tipa. Ali onda se javlja drugi problem – imaćemo za svaki tip objekta po jednu klasu koja definiše odgovarajuću kolekciju. Drugi, efikasniji način, su upravo generički tipovi. Oni omogućuju da se definiše samo jedna klasa kojom je kolekcija predstavljena. Svi objekti u kolekciji su istog tipa, ali sada postoji mogućnost da taj tip može biti proizvoljan. Na ovaj način, imamo jednu kolekciju koja može da se ponaša i kao kolekcija tačaka, i kao kolekcija duži i kao kolekcija stringova itd. Generički tip je klasni ili interfejsni tip koji ima jedan ili više parametara. Definicija konkretne klase ili interfejsa iz generičkog tipa vrši se dodavanjem konkretnog argumenta za svaki od parametara generičkog tipa.public class LinkedList<T>{ … }Parametar T se upotrebljava u definiciji metoda i polja gde postoji zavisnost od tipa ovog argumenta. Pojave ovog parametra u definiciji generičkog tipa se označavaju kao promenljive tipa, jer će biti zamenjene konkretnom vrednošću tog tipa, na isti način kao što parametri metoda bivaju zamenjeni argumentum koji se prosledi metodu. Kreiranje klase iz datog generičkog tipa vrši se navođenjem odgovarajućeg argumenta za parameter unutar <>. Sva pojavljivanja parametra T u definiciji generičkog tipa biće zamenjena datim argumentum. Generički tip na taj način definiše skup tipova, dobijenih za različite vrednosti parametra tipa T. Kao argument za parameter T može se proslediti samo klasni ili interfejsni tip. Primitivni tipovi ne mogu da se koriste. Umesto njih koristimo odgovarajuće klase koje ih enkapsuliraju. Generički tip -{LinkedList<T>}- koji čuva -{double}- vrednosti:LinkedList<Double> temp= new LinkedList<Double>(); temp.addItem(10.8)Pošto je tip parametra -{Double}- kompajler automatski umeće konverziju vrednosti 10.8 iz -{double}- u -{Double}-. Možemo da definišemo generički tip koji definiše skup klasa koje obuhvataju par objekata proizvoljnog tipa. U tom slučaju definišemo generički tip sa dva parametra:public class Par <TipKljuca, TipVrednosti> {
public Par(TipKljuca kljuc, TipVrednosti vr) {
this.kljuc = kljuc; this.vr = vr;
… private TipKljuca kljuc; private TipVrednosti vr; }Upotreba:Par <String,String>unos =new Par<String,String>(“Milan”, “4445555”);Oblast važenja parametarskog tipa je cela generička klasa, isključujući statičke članice klase. Statička polja ne mogu biti definisana promenljivom tipa, niti statički metodi mogu da imaju parametre ili povratne vrednosti koje su parametarskog tipa. Ukoliko generička klasa sadrži neko statičko polje, svaki tip proizveden iz datog generičkog tipa, imaće svoju kopiju statičkog polja. Neka generička klasa -{LinkedList<>}- sadrži statičko polje -{count}- za brojanje kreiranih objekata. Svaka klasa dobijena iz generičke za konkretan argument tipa imaće svoju kopiju ovog polja. Na taj način,
-{count}- u klasi -{LinkedList<String>}- broji kreirane objekte ove klase
-{count}- u klasi -{LinkedList<Point>}- broji kreirane objekte ove klase itd.
Generički metod Možemo da definišemo metod sa svojim nezavisnim skupom od jednog ili više parametara. Takav metod naziva se parametarski metod ili generički metod. Generički metod može da postoji i unutar obične klase.public static <T> void ispisiSve(LinkedList lista){ for(T objekat: lista) System.out.println(objekat); }<T> ispred koga se navodi -{public}- ili -{static}- ključna reč predstavlja parametarski tip (ili listu tipova) za generički metod. Ovde imamo samo jedan parametarski tip za metod -{ispisiSve}-, ali ih može biti više. Lista parametarskih tipova se uvek pojavljuje između uglastih zagrada i navodi se iza kvalifikatora pristupa, a pre povratne vrednosti metoda. Argument tipa će biti određen na osnovu parametra tipa koji je prosleđen pri pozivu metoda.
Nizovi i parametrizovani tipovi Nizovi elemenata tipa dobijenog od generičkog tipa nisu dopušteni!
Kolekcije -{Java system}- kolekcija je skup generičkih tipova koje koristimo za kreiranje kolekcijskih klasa. Kolekcijska klasa je klasa koja organizuje skup objekata datog tipa na određen način (npr. lančane liste, stek,….). Najveći broj ovih klasa koje čine sistem kolekcija definisane su u paketu -{java.util}-. Razmotrićemo sledeće generičke tipove: -{Iterator<T>}- interfejsni tip - deklariše metode za iteriranje kroz jedan po jedan element kolekcije
-{ArrayList<T>}- tip - ima strukturu dinamičkog niza, broj objekata koje možemo sačuvati se automatski povećava po potrebi.
-{LinkedList<T>}- tip - dvostruko povezana lista, omogućeno je iteriranje u oba smera Klasu koja definiše kolekciju objekata često nazivamo kontejnerskom klasom. Postoje tri osnovna tipa kolekcija koji organizuju objekte na različite načine: skupovi
nizovi
katalozi
Skup