0d4b9e6364ede5b9ebaa709a20d7cc5f.ppt
- Количество слайдов: 69
Objektno orijentisano programiranje Java, interfejsi
Određivanje klase objekta nastavak ● ● ● Iako se može koristiti metod for. Name() da bi se dobio Class objekat koji odgovara određenoj klasi ili interfejsu, postoji direktniji način Ako nadovežemo. class na ime proizvoljne klase, interfejsa ili primitivnog tipa, imamo referencu na Class objekat te klase npr. java. lang. String. class referiše Class objekat za klasu String, dok Pas. class referiše Class objekat za klasu Pas int. class je Class objekat za primitivni tip int Ovo možda u ovom trenutku ne izgleda posebno značajno, ali treba ga imati na umu
Određivanje klase objekta nastavak ● ● ● Pošto postoji samo po 1 Class objekat za svaku klasu ili interfejs, klasa objekta se može testirati programski Zivotinja ljubimac; može se proveriti da li referiše na objekat klase Pas na sledeći način: if(ljubimac. get. Class()==Pas. class) System. out. println(”To je pas!”); Ako ljubimac sadrži referencu na objekat potklase Pas, rezultat poređenja u if-u je false U Javi postoji operator instanceof koji radi skoro istu stvar, ali ne i potpuno istu
Klasa Class ● ● ● Klasa Class nije obična klasa To je primer generičkog tipa ( zapravo definiše skup klasa) Svaka klasa, interfejs, niz i primitivni tip koji koristimo u programu biće predstavljen objektom jedinstvene klase iz skupa definisanog Class generičkim tipom
Kopiranje objekata ● ● ● protected metod clone() koji je nasleđen od klase Object kreiraće novi objekat koji je kopija tekućeg objekta On će to uraditi jedino ako klasa objekta koji treba klonirati ukazuje da je kloniranje dopušteno, a to je slučaj kada klasa implementira interfejs Cloneable Metod clone() nasleđen od Object klonira objekat kreiranjem novog objekta istog tipa kao što je tekući objekat i postavljajući svako od polja novog objekta na istu vrednost koju ima odgovarajuće polje tekućeg objekta
Kopiranje objekata ● ● Kada atributi tekućeg objekta referišu na objekte klasa, objekti na koje oni referišu se NE DUPLIRAJU prilikom kreiranja klona – samo se reference iskopiraju iz polja starog objekta u polja kloniranog objekta To tipično nije ono što želimo da se desi – i novi i stari objekat onda mogu menjati taj deljeni objekat na koji se referiše preko njihovih odgovarajućih atributa, bez registrovanja da se to dešava
Kopiranje objekata ● ● ● Ako objekte klase treba klonirati, klasa mora implementirati Cloneable interfejs (implementiranje interfejsa obično uključuje implementiranje specifičnog skupa metoda ) Sve što je potrebno da bi klasa implementirala ovaj interfejs jeste da se to deklariše u prvoj liniji definicije klase pomoću ključne reči implements: class Pas implements Cloneable{ // Detalji definicije klase. . . } onda se objekti klase Pas mogu klonirati primer: kloniranje
Komentarisanje primera ● Pas. Ljubimac objekat može se kreirati sa: Pas. Ljubimac moj. Ljubimac = new Pas. Ljubimac(”Maza”, ”Mesanac”); ● Ako želite istog takvog, možemo ga klonirati: Pas. Ljubimac vas. Ljubimac = (Pas. Ljubimac)moj. Ljubimac. clone(); ● ● Sada imamo 2 Pas. Ljubimac objekta koji sadrže referencu na isti objekat Buva Metod clone() će kreirati novi Pas. Ljubimac objekat vas. Ljubimac i kopirati referencu na objekat Buva iz atributa buva objekta moj. Ljubimac u istoimeni atribut objekta vas. Ljubimac
Komentarisanje primera ● ● ● ime ljubimca može se promeniti naredbom: vas. Ljubimac. set. Ime(”Lunja”); Vaš pas će verovatno želeti sopstvenu buvu : ) , pa možemo promeniti ime njegove buve naredbom: vas. Ljubimac. get. Buva(). set. Ime(”Buvica”); Nažalost : ) , Mazina buva će se takođe preimenovati u Buvica, pošto Maza i Lunja dele zajedničku buvu
clone() , kopi-konstruktor ● ● ● Osim ako stvarno želimo da atributi dva različita objekta dele objekte na koje referišu, trebalo bi da metod clone() u našoj klasi implementiramo tako da radi kloniranje na odgovarajući način Kao alternativa kloniranju ( ili zajedno sa njim ) možemo našoj klasi da dodamo konstruktor koji kreira novi objekat od postojećeg Ako implementiramo našu sopstvenu public verziju clone() metoda da predefinišemo nasleđenu verziju, tipično ćemo kodirati ovaj metod na isti način kao što bismo i kopi -konstruktor
Primer clone() metoda ● clone() metod klase Pas. Ljubimac mogao bi biti: public Object clone() throws Clone. Not. Supported. Exception{ Pas. Ljubimac ljubimac = new Pas. Ljubimac(ime, rasa); ljubimac. set. Ime(”Lunja”); ljubimac. get. Buva(). set. Ime(”Buvica”); return ljubimac; } ● Ovde metod kreira novi Pas. Ljubimac objekat koristeći ime i rasu tekućeg objekta
Primer clone() metoda ● Mogli smo koristiti i nasleđeni clone() metod da dupliramo tekući objekat i onda da eksplicitno kloniramo član buva da bi referisao na nezavisan objekat public Object clone() throws Clone. Not. Supported. Exception{ Pas. Ljubimac ljubimac = (Pas. Ljubimac)super. clone(); ljubimac. buva = (Buva)buva. clone(); return ljubimac; ● ● ● } Novi objekat kreiran nasleđenim clone() metodom je objekat klase Pas. Ljubimac, ali se vraća kao referenca klase Object Za pristup članu buva neophodna je referenca na Pas. Ljubimac pa je ovde kastovanje od ključnog značaja Efekat ove verzije clone() metoda je isti kao i prethodne verzije
Metodi sa promenljivim brojem argumenata ● ● Moguće je pisati metod koji prima proizvoljan broj argumenata pri pozivu i argumenti koji se prosleđuju ne moraju biti istog tipa Da bi se razumelo kako to funkcioniše, neophodno je razumeti ulogu klase Object. Da metod očekuje proizvoljan broj argumenata ukazuje se navođenjem poslednjeg parametra u obliku Object. . . args Metod može imati 0 ili više parametara pre ovoga, a ovaj mora biti poslednji
Metodi sa promenljivim brojem argumenata ● ● ● Ime parametra args predstavlja niz tipa Object[] i vrednosti argumenata su dostupne u elementima niza kao tip Object. Unutar tela metoda, dužina niza args nam kaže koliko argumenata je prosleđeno. Jednostavan primer: želimo da implementiramo statički metod koji će prihvatati proizvoljan broj argumenata i ispisivati argumente u komandnu liniju – šta god da su public static void print. All(Object. . . args){ for(Object arg : args) System. out. print(” ” + arg); System. out. println(); } ● ● argumenti mogu biti sasvim proizvoljnih tipova na vrednosti primitivnih tipova biće primenjen autoboxing, jer metod očekuje reference kao argumente
Metodi sa promenljivim brojem argumenata ● ● Jedna od upotreba mogućnosti promenljive liste argumenata u bibliotekama klasa jeste za definisanje printf() metoda u Print. Stream klasi. Ovaj metod proizvodi formatirani izlaz za proizvoljnu sekvencu vrednosti proizvoljnih tipova, pri čemu je formatiranje određeno prvim argumentom metoda System. out je tipa Print. Stream, pa se printf() može koristiti da proizvede formatirani izlaz u komandnoj liniji
Ograničavanje tipova u promenljivoj listi argumenata ● ● Tip promenljive liste argumenata ne mora biti Object, možemo staviti proizvoljnu klasu ili interfejs. Argumenti onda moraju biti tog zadatog tipa ili njegovog proizvoljnog podtipa. Ako se stavi da je tip Object, maksimizuje se fleksibilnost jer se mogu prosleđivati argumenti proizvoljnog tipa, ali u mnogim situacijama želimo da ograničimo koji tipovi argumenata mogu da se proslede Primer: ako hoćemo da definišemo metod koji računa prosečnu vrednost proizvoljnog broja vrednosti koje se zadaju kao posebni argumenti, onda želimo da budemo sigurni da su argumenti isključivo numeričke vrednosti
Primer public static double average(Double. . . args){ if(args. length = = 0) return 0; double avg = 0. 0; for(double value : args) ave += value; return ave/args. length; } ● ● U ovom slučaju, argumenti moraju biti tipa Double ili tipa izvedenog iz Double ili – zbog autoboxing konverzije obezbeđene kompajlerom – tipa double Ako bismo pokušali da prosledimo vrednosti tipa int kao argumente metoda, kompajler bi to označio kao grešku, jer ne postoji automatska konverzija tipa int u tip Double
Kastovanje objekata ● Moguće je kastovati objekat u tip neke druge klase, ali jedino ako su klasa tekućeg objekta i nova klasa u istoj hijerarhiji izvedenih klasa i jedna je superklasa druge Object Zivotinja Macka Pas Sarplaninac Patka
Kastovanje objekata ● ● Moguće je kastovati referencu na objekat klase naviše u tip njene direktne ili indirektne superklase. Npr. moguće je kastovati referencu na objekat tipa Sarplaninac direktno u tip Pas, Zivotinja ili Object. Sarplaninac ljubimac = new Sarplaninac(”Sarko”); Zivotinja zivotinja = (Zivotinja) ljubimac; // kastovanje Sarplaninac Zivotinja ● Kada izvršimo dodelu reference na objekat promenljivoj tipa superklase, nije neophodno eksplicitno pisati kastovanje, može se pisati i samo: Zivotinja zivotinja = ljubimac; ● Kompajler će uvek po potrebi sam ubaciti kastovanje u tip superklase
Kastovanje objekata ● ● ● Kada kastujemo referencu na objekat u tip superklase, Java pamti stvarnu klasu kojoj taj objekat pripada. Da to nije slučaj, polimorfizam ne bi bio moguć. Pošto se pamti informacija o originalnom tipu objekta, moguće je i kastovanje naniže kroz hijerarhiju klasa Ipak, kastovanje naniže se uvek mora izvršiti eksplicitno pošto ga kompajler ne umeće sam. Da bi kastovanje radilo, objekat mora biti legitimna instanca klase u koju kastujemo, tj. klasa u koju kastujemo mora biti stvarna klasa objekta, ili superklasa objekta
Kastovanje objekata ● ● Npr. moguće je kastovanje reference iz promenljive zivotinja u tip Pas ili Sarplaninac, pošto je početno objekat bio Sarplaninac, ali nije moguće kastovanje u Mačka ili Patka pošto objekat klase Sarplaninac nema klase Mačka ili Patka za superklase Za kastovanje zivotinja u tip Pas možemo pisati: Pas pas = (Pas) zivotinja; // kastovanje Zivotinja ● Pas Upamtiti da se promenljiva pas može koristiti samo za poziv polimorfnih metoda klase Sarplaninac koji predefinišu metode koji postoje u Pas. Ne mogu se pozivati metodi koji nisu definisani u klasi Pas. Ako želimo da zovemo metod koji postoji u klasi Sarplaninac, a ne i u klasi Pas, moramo prvo kastovati pas u tip Sarplaninac
Kastovanje objekata ● ● ● Iako nije moguće kastovanje objekata koji nisu u vezi, npr. Sarplaninac Patka, moguće je postići konverziju pisanjem odgovarajućeg konstruktora, ali samo gde ima smisla raditi tako nešto Napiše se konstruktor u klasi u koju hoćemo da konvertujemo i on treba da prihvata objekat klase iz koje konvertujemo kao argument Ono što u tom slučaju radi konstruktor je prilično različito u odnosu na kastovanje. Ovde se kreira potpuno nov objekat koji je odvojen od originala, dok kastovanje predstavlja isti objekat kao različit tip
Kada kastovati objekte? ● ● ● Postojaće razlozi za kastovanje objekata u oba pravca kroz hijerarhiju klasa npr. kadgod polimorfno izvršavamo metode, smeštamo objekte u promenljivu bazne klase i pozivamo metode izvedene klase. To u opštem slučaju uključuje kastovanje objekata izvedene klase u baznu klasu Drugi razlog za kastovanje naviše kroz hijerarhiju je da bi se prosledio objekat neke od mogućih potklasa metodu. Zadavanjem da je parametar baznog tipa imamo fleksibilnost da metodu prosledimo objekat proizvoljne izvedene klase npr. ako je parametar tipa Zivotinja, možemo proslediti objekat tipa Pas, Patka ili Mačka
Kada kastovati objekte? ● ● Razlog zbog koga možda želimo da kastujemo naniže kroz hijerarhiju klasa je da bismo izvršili metod specifičan za određenu klasu. Ako klasa Patka ima metod snesi. Jaje(), npr. ne možemo ga pozivati koristeći promenljivu tipa Zivotinja, čak i ako ona referiše na objekat klase Patka. Kao što je rečeno, kastovanje naniže kroz hijerarhiju klasa uvek zahteva eksplicitno kastovanje Primer: Patka patka = new Patka(”Pata”, ”Polarna patka”); Zivotinja ljubimac = patka; // kastovanje Patke u Zivotinju ljubimac. snesi. Jaje(); // Ovo se nece iskompajlirati!!! dobićemo poruku kompajlera da metod snesi. Jaje() nije nađen u klasi Zivotinja
Kada kastovati objekte? ● ● Pošto znamo da je ovaj objekat stvarno Patka, možemo izmeniti prethodni fragment koda tako da radi, tako što bi poziv metoda trebalo da bude: ((Patka)ljubimac). snesi. Jaje(); // ovo radi objekat na koji pokazuje ljubimac prvo se kastuje u tip Patka. Rezultat kastovanja se zatim koristi za poziv metoda snesi. Jaje(). Da objekat nije bio tipa Patka, kastovanje bi uzrokovalo izbacivanje izuzetka
Preporuka ● ● Generalno, trebalo bi izbegavati eksplicitno kastovanje objekata koliko je to moguće, jer ono uvećava mogućnost nekorektnog kastovanja, što naše programe čini nepouzdanim. Ako smo pažljivo dizajnirali naše klase, eksplicitna kastovanja nam neće prečesto biti neophodna.
Identifikovanje objekata ● ● ● Postoje okolnosti kada možda ne znamo tačno sa kojim tipom objekata rukujemo. To se može desiti ako se objekat izvedene klase prosledi metodu kao argument za parametar tipa bazne klase U nekim situacijama možda je neophodno kastovati objekat u njegov stvarni tip, recimo da bi se pozvao metod specifičan za klasu. Ako pokušamo da izvršimo kastovanje i ispostavi se da je to ”ilegalno”, biće izbačen izuzetak i naš program će se završiti, osim ako nismo obezbedili hvatanje izuzetaka. Jedan od načina da se to izbegne je proveriti da je objekat tipa koji očekujemo pre nego što izvršimo kastovanje
Identifikovanje objekata ● ● ● Ranije smo videli da možemo da koristimo metod get. Class() da bismo dobili Class objekat koji odgovara klasi i kako možemo da ga uporedimo sa instancom klase Class za klasu koju tražimo. To se takođe može uraditi korišćenjem operatora instanceof Pretpostavimo da imamo promenljivu ljubimac tipa Zivotinja i želimo da je kastujemo u tip Patka. Može ovako: Patka patka; if(ljubimac instanceof Patka) { patka = (Patka) ljubimac; patka. snesi. Jaje(); }
Identifikovanje objekata Ako ljubimac ne referiše na Patka objekat, pokušaj kastovanja objekta referisanog pomoću ljubimac u Patka doveo bi do izbacivanja izuzetka ● Prethodni fragment koda može i kompaktnije da se zapiše: if(ljubimac instanceof Patka) ((Patka)ljubimac). snesi. Jaje(); ● KOJA JE RAZLIKA IZMEĐU instanceof i get. Class()? Razlika je prilično suptilna: instanceof operator proverava da li je kastovanje objekta referisanog levim operandom u tip zadat desnim operandom dopušteno. Rezultat će biti true ako je objekat istog tipa kao desni operand ili ako je tipa proizvoljne njegove potklase Razlika se može ilustrovati neznatnom izmenom jednog jednostavnog primera ( pretpostavka je da se u ljubimac nalazi referenca na objekat tipa Sarplaninac. Želimo da pozovemo metod definisan u klasi Pas, pa je potrebno da proverimo da ljubimac zaista referiše na Pas objekat ) ●
instanceof vs. get. Class() if(ljubimac instanceof Pas) System. out. println(”Imate psa!”); else System. out. println(”Definitivno nije pas!”); ● ● ● Dobićemo potvrdu da imamo Pas objekat čak i ako je to stvarno Sarplaninac objekat Ovo je dobro za potrebe kastovanja! Međutim, ako napišemo: if(ljubimac. get. Class() = = Pas. class) System. out. println(”Imate psa!”); else System. out. println(”Definitivno nije pas!”); izraz if će imati vrednost false jer je klasa objekta Sarplaninac, pa je njen Class objekat različit od onoga za Pas. class – morali bismo da napišemo Sarplaninac. class da bi izraz if imao vrednost true.
Zaključak: instanceof ili get. Class() ● ● Za svrhe kastovanja treba uvek koristiti operator instanceof da bi se proverio tip reference Kada je potrebno potvrditi tačan tip reference, treba proveriti Class objekat odgovarajući za referencu
Dizajniranje klasa ● ● ● Osnovni problem u OOP jeste odlučiti na koji način treba da budu povezane klase našeg programa. Jedna mogućnost jeste da se kreira hijerarhija klasa njihovim izvođenjem iz bazne klase koju smo definisali i dodavanjem metoda i atributa da specijalizujemo potklase. Jedan primer ovoga su klasa Zivotinja i potklase izvedene iz nje Druga mogućnost je definisati skup klasa koje nisu u hijerarhiji, ali koje imaju atribute koji su sami po sebi objekti. Npr. klasa Zoo može kao atribute imati objekte klasa izvedenih iz klase Zivotinja
Dizajniranje klasa ● ● ● Možemo imati i hijerarhije klasa koje sadrže atribute koji su objekti – to već imamo kod klasa izvedenih iz Zivotinja pošto one imaju atribute tipa String. Kod primera koje smo videli do sada, bilo je očigledno koji pristup odabrati, međutim, nije uvek tako. Prilično često treba izabrati između definisanja klasa kao hijerarhije i definisanja klasa sa atributima koji su objekti. Šta je najbolji pristup? Nema striktnog pravila. Postoje, ipak, neke smernice i neki konteksti u kojima je izbor očigledniji Osim želje da se odraze odnosi između tipova i objekata koji važe u stvarnosti, potreba za korišćenjem polimorfizma je primarni razlog za upotrebu potklasa ili interferjsa. To je suština OO programiranja Posedovanje povezanih objekata koji se mogu ekvivalentno tretirati može dosta uprostiti naše programe
PRIMER ● ● Mnoge situacije uključuju prosuđivanje o dizajnu naših klasa. Razmotrimo opcije u praksi na jednostavnom primeru. Želimo da definišemo klasu Poly. Line da bismo predstavili geometrijske entitete koji se sastoje od povezanih duži(segmenata), tj. poligonalne linije Deluje razumno da se tačke predstavljaju kao objekti klase Point. Tačke su dobro definisani objekti koji će se javljati u kontekstu svih vrsta geometrijskih entiteta
Primer ● ● Sledeće pitanje koje možemo da postavimo moglo bi da bude: treba li da izvedemo klasu Poly. Line iz klase Point? Odgovor je prilično očigledan: poligonalna linija jasno nije vrsta tačke, pa nije logično izvesti klasu Poly. Line iz klase Point. Ovo je elementarna demonstracija ”je”-testa. Ako možemo reći da: jedna vrsta objekta ”je” specijalizovana forma druge vrste objekta, imamo dobar slučaj za izvođenje klase ( ali ne uvek – mogu postojati i kontrarazlozi ). Ako ne možemo to reći – ništa od izvođenja! Komplement ”je”-testa je ”ima”-test. Ako jedan objekat ”ima” komponentu koja je objekat druge klase, imamo situaciju za atribut koji je tipa klase.
Primer ● ● ● Objekat House ”ima” vrata, pa bi trebalo da promenljiva tipa Door bude član klase House. Klasa Poly. Line će sadržati nekoliko tačaka, što deluje obećavajuće, ali treba pobliže pogledati kako ćemo ih čuvati, pošto postoji nekoliko opcija. DIZAJNIRANJE KLASE Poly. Line Niz objekata tipa Point deluje kao dobar kandidat za atribut klase. Međutim, postoje mane toga. Uobičajeni zahtev za poligonalne linije je mogućnost dodavanja segmenta postojećem objektu. Sa nizom za smeštanje tačaka, morali bismo da kreiramo novi niz svaki put kada dodamo segment, a zatim da kopiramo sve tačke iz starog niza u novi. To može trošiti dosta vremena ako imamo Poly. Line objekat sa velikim brojem segmenata
DIZAJNIRANJE KLASE Poly. Line ● ● Imamo još jednu opciju, a to je povezana lista tačaka. U svojoj najjednostavnijoj formi, povezana lista objekata je uređenje u kome svaki objekat liste sadrži referencu na sledeći objekat kao atribut. Sve dok imamo promenljivu koja sadrži referencu na prvi Point objekat, možemo pristupiti svim tačkama u listi
Dizajniranje klase Poly. Line ● ● ● Tačke se čuvaju kao članovi List. Point objekta. Osim konstruktora, klasa Poly. Line će trebati i metod za dodavanje tačaka. Možemo koristiti jedan od najmanje tri pristupa za definisanje klase List. Point. Mogu se navesti razlozi u korist sva 3. 1. u klasi List. Point koordinate x i y se mogu čuvati eksplicitno. Glavni razlog protiv ovoga bio bi da već imamo enkapsulirana svojstva tačke u klasi Point, zašto to ne iskoristiti? 2. možemo posmatrati List. Point objekat kao nešto sadrži referencu na Point objekat, plus članove koji referišu na prethodni i naredni objekat u klasi. To nije nerazuman pristup i lako se implementira. 3. List. Point objekat možemo posmatrati i kao specijalizovanu vrstu Point objekta, pa bismo mogli da izvedemo List. Point klasu iz klase Point. Da li je ovo razumno ili ne zavisi od našeg gledanja na to.
Dizajniranje klase Poly. Line ● ● ● Odlučimo se za drugu varijantu. Klasa List. Point se može implementirati sa atributom tipa Point, koji definiše osnovnu tačku i njene koordinate. List. Point objekat će imati i atribut next, tipa List. Point kome je svrha da sadrži referencu na sledeći objekat u klasi. Za neki List. Point objekat znaćemo da je poslednji element liste ako je vrednost njegovog next atributa null. Point. java List. Point objekat je sredstvo za kreiranje liste Point objekata koji potiču od kojekuda, tako da ne moramo da brinemo oko dupliranja Point objekata smeštenih u listi. Možemo sačuvati referencu na Point objekat prosleđenu konstruktoru u atribut point. Atribut next treba da sadrži referencu na sledeći List. Point u listi, i pošto on ovde nije definisan, postavljamo next na null.
Dizajniranje klase Poly. Line ● ● ● get. Next() metod omogućuje da se odredi sledeća tačka u listi, pa je ovaj metod sredstvo pomoću kojeg se može iterirati kroz celu listu Poly. Line. java Klasa Poly. Line ima 2 atributa start i end i oni referišu na prvu i poslednju tačku u listi ili null ako je lista prazna Čuvanje krajnje tačke u listi nije ključno, pošto uvek možemo da je nađemo idući kroz listu počev od start. Međutim, posedovanje reference na krajnju tačku štedi mnogo vremena kada želimo da dodamo tačku u listu. Konstruktor prihvata niz Point objekata i počinje proces sklapanja objekta kreirajući listu koja sadrži jedan Line. Point objekat, kreiran od prvog elementa niza, a zatim koristi metod add. Point() da doda sve preostale tačke niza u listu.
Dizajniranje klase Poly. Line ● ● Dodavanje tačke u listu: sve što add. Point() metod radi je da kreira List. Point objekat od Point objekta prosleđenog kao argument, postavlja atribut next stare krajnje tačke u listi da referiše na novu tačku i, konačno, smešta referencu na novu krajnju tačku u atribut end. Test. Poly. Line. java Ova klasa radi dovoljno dobro, ali nije sasvim objektnoorijentisana Bolji pristup je napraviti povezanu listu opšte namene. ( Tu bi elementi liste bili objekti klase Object. Pošto je ona superklasa svake klase, u promenljivoj tipa Object može se čuvati proizvoljna vrsta objekta )
final modifajer ● ● ● final – za fiksiranje vrednosti statičkog atributa klase Ključna reč final može se koristiti i u definiciji metoda i u definiciji klase Ako želimo da sprečimo da se u potklasi predefiniše metod klase, taj metod treba deklarisati kao final u baznoj klasi. Pokušaj da se predefiniše final metod u potklasi rezultovaće označavanjem novog metoda kao greške od stane kompajlera. Očigledno, apstraktni metod ne može biti deklarisan kao final jer mora biti definisan u nekoj potklasi. Ako deklarišemo klasu kao final, sprečićemo da proizvoljna potklasa može da se izvede iz nje.
final modifajer ● ● ● public final class Poly. Line{ // Definicija klase kao ranije. . . } Ako onda pokušamo da izvedemo klasu iz ove klase, kompajler će prijaviti grešku. Apstraktna klasa ne može biti deklarisana kao final pošto će to sprečiti da apstraktni metodi klase budu ikada definisani. Deklarisanje klase kao final je drastičan korak koji sprečava da funkcionalnost klase bude proširena izvođenjem, pa treba da budemo potpuno sigurni da je to ono što želimo.
Interfejsi ● ● ● U klasama koje smo izveli iz klase Zivotinja, imali smo zajednički metod zvuk(), koji je individualno implementiran u svakoj od potklasa. Potpis metoda je bio isti u svakoj klasi i metod se mogao polimorfno pozivati. Glavni razlog da se najpre definiše klasa Zivotinja, a zatim Pas, Macka itd. iz nje, jeste da bismo mogli da koristimo polimorfizam. Kada je sve što želimo skup jednog ili više metoda koje treba implementirati u različitim klasama tako da možemo da ih zovemo koristeći polimorfizam, možemo koristiti tzv. interfejs.
Interfejsi ● ● ● Interfejs je, u suštini, kolekcija povezanih konstanti i/ili apstraktnih metoda i u većini slučajeva sadrži samo metode. Interfejs ne definiše šta radi metod. On samo definiše njegov oblik – ime, parametre, povratni tip, tako da su po definiciji metodi u interfejsu apstraktni. Da bismo koristili interfejs, implementiramo interfejs u klasi, tj. deklarišemo da klasa implementira interfejs i pišemo kod za svaki od metoda koji interfejs deklariše kao definicije klase. Kada klasa implementira interfejs, sve konstante koje su eventualno definisane u definiciji interfejsa direktno su dostupne u klasi, upravo kao da su nasleđene iz bazne klase
Interfejsi ● ● Interfejs može sadržati konstante, apstraktne metode ili i jedne i druge Metodi u interfejsu su uvek public i abstract, pa ne moramo to eksplicitno da navodimo Smatra se lošom programerskom praksom zadavati bilo kakav atribut za njih, i definitivno se ne mogu dodati drugi atributi osim tih podrazumevanih (public, abstract). Ovo povlači da metodi deklarisani u interfejsu nikada NE MOGU biti statički Konstante u interfejsu su uvek public, static i final, pa ni za njih nema potrebe navoditi eksplicitno ove atribute
Interfejsi ● ● Interfejs se deklariše kao i klasa, ali koristeći ključnu reč interface umesto class. Definiciju interfejsa čuvamo u fajlu sa ekstenzijom. java i imenom istim kao i ime interfejsa. Ime koje damo interfejsu mora se razlikovati od imena ostalih interfejsa i klasa istog paketa.
Enkapsuliranje konstanti u programu ● ● ● Često se dešava da program koristi skup konstantnih vrednosti koje želimo da definišemo samo jednom U verzijama Jave pre 5. 0, uobičajeni pristup bio je da se definiše skup povezanih konstanti u interfejsu, a zatim implementira interfejs u svakoj klasi koja koristi neku od tih konstanti. Taj pristup je zastareo zbog mogućnosti static import-a Mogućnost importovanja statičkih članova klase, uvedena u Javi 5, obezbeđuje odličan način rukovanja konstantama u programu. Međutim, upotreba interfejsa u te svrhe je u prošlosti bila vrlo rasprostranjena Upotreba static import-a za pristup konstantama definisanim u nekoj klasi je mnogo čistiji i bolji način da se skup konstanti učini dostupnim gde god je potreban.
Konstante u interfejsu public interface Konstante{ double PI = 3. 14; double E = 2. 72; double TRIPLE_E=3*E; } ● ● ● Pošto su konstante u interfejsu uvek static i final ( i public, ali to u ovoj rečenici nije bitno), moramo uvek obezbediti inicijalne vrednosti za te konstante. Vrednost jedne konstante može se izraziti u terminima neke prethodne kao što je to slučaj sa TRIPLE_E. Pošto smo interfejs deklarisali kao public, konstante su dostupne i izvan paketa u kome se nalazi interfejs.
Konstante u interfejsu ● Možemo pristupati konstantama definisanim u interfejsima na isti način kao i statičkim i public atributima klase – kvalifikovanjem njihovih imena imenom interfejsa npr. public class Moja. Klasa{. . . return r * Konstante. PI; . . . } ● Pošto interfejs Konstante uključuje samo konstante, klasa im može pristupiti koristeći njihova nekvalifikovana imena deklarisanjem da klasa implementira ovaj interfejs. To je bila tehnika korišćena u prošlosti npr. public Moja. Druga. Klasa implements Konstante{. . . return r * PI; . . . } ● Konstante definisane u interfejsu Konstante su sada članovi klase Moja. Druga. Klasa i biće nasleđeni u proizvoljnoj izvedenoj klasi
Konstante definisane u klasi ● ● Možemo definisati klasu koja će sadržati isti skup konstanti kao interfejs: package matematika; public class Konstante{ public static final double PI = 3. 14; public static final double E = 2. 72; public static final double TRIPLE_E = 3 * E; } Naravno, možemo pristupati članovima klase Konstante izvan te klase kvalifikovanim imenom klase, npr. Konstante. TRIPLE_E
Konstante definisane u klasi ● ● Alternativa i uobičajeniji pristup je importovanje statičkih članova klase u proizvoljnu klasu koja koristi neku od ovih konstanti. To će omogućiti korišćenje konstanti pomoću njihovih nekvalifikovanih imena. U ovom slučaju, klasa mora biti u imenovanom paketu, jer import deklaracija ne može da se primeni na bezimeni paket npr. import static matematika. Konstante. *; public class Moja. Druga. Klasa{. . . return r * PI; }
Interfejsi koji deklarišu metode ● ● ● Primarna svrha interfejsa je da definiše eksterni oblik skupa metoda koji predstavljaju određenu funkcionalnost Svaki metod deklarisan u interfejsu mora imati definiciju unutar klase koja implementira taj interfejs ako hoćemo da kreiramo konkretne objekte te klase Pošto su metodi u interfejsu po definiciji public, moramo koristiti ključnu reč public kada ih definišemo u našoj klasi – inače se kod neće iskompajlirati. Implementacija metoda interfejsa u klasi ne sme imati pristupni atribut koji je restriktivniji od implicitnog u deklaraciji apstraktnog metoda, a ne postoji manje restriktivan atribut od public.
Interfejsi koji deklarišu metode ● ● Klasa može implementirati više od jednog interfejsa. U tom slučaju, pišemo imena svih interfejsa koje klasa implementira razdvojene zapetama nakon ključne reči implements. Npr. public class Moja. Klasa implements Int 1, Int 2, Int 3 {. . . } Ova klasa implementira 3 interfejsa. Telo klase sadržaće definicije metoda deklarisanih u sva 3 interfejsa Ne mora se implementirati svaki metod interfejsa, ali onda postoje neke posledice toga
Delimična implementacija interfejsa ● ● Moguće je izostaviti implementaciju jednog ili većeg broja metoda interfejsa u klasi koja implementira interfejs, ali u tom slučaju klasa nasleđuje neke apstraktne metode od interfejsa, pa moramo deklaristi samu klasu kao apstraktnu: import static matematika. Konstante. E; import static matematika. Konstante. PI; public abstract class Moja. Klasa implements Naredbe{. . . } i neka npr. klasa Moja. Klasa implementira samo neke od metoda interfejsa Naredbe Ne možemo kreirati objekte (apstraktne) klase Moja. Klasa Da bismo dospeli do korisne klase, moramo definisati potklasu klase Moja. Klasa koja implementira preostale metode interfejsa. Deklaracija klase kao abstract je obavezna kada ne implementiramo sve metode deklarisane u interfejsu. Kompajler će se buniti ako to zaboravimo.
Cloneable interfejs ● ● ● Rečeno je ranije da moramo da implementiramo interfejs Cloneable da bismo koristili nasleđeni metod clone(). Zapravo, ovaj interfejs je prazan, bez metoda ili konstanti, pa je sve što je potrebno da bi se implementirao u klasi, navesti da ga ta klasa implementira. To znači da je samo potrebno napisati nešto nalik: public Moja. Klasa implements Cloneable{. . . } Jedina svrha interfejsa Cloneable je da glumi fleg koji signalizira da li smo spremni da dozvolimo da objekti naše klase budu klonirani ili ne. Čak i ako smo definisali public clone() metod, kompajler neće dopustiti da se taj metod poziva za objekte naše klase osim ako takođe ne zadamo da naša klasa implementira Cloneable interfejs.
Izvođenje iz interfejsa ● ● ● Možemo definisati jedan interfejs na osnovu drugog korišćenjem ključne reči extends za identifikovanje imena baznog interfejsa. To je istog oblika koji koristimo da izvedemo jednu klasu iz druge Interfejs koji se izvodi dobija sve metode i konstante od interfejsa iz koga se izvodi public interface Konverzija extends Konstante{. . . } Interfejs iz koga se vrši izvođenje naziva se i super-interfejsom za interfejs koji se izvodi.
Interfejsi i višestruko nasleđivanje ● ● Za razliku od klase koja može da nasledi samo jednu klasu, interfejs može da nasledi proizvoljan broj interfejsa. Da bismo definisali interfejs koji nasleđuje članove nekoliko drugih interfejsa, zadajemo imena tih interfejsa razdvojena zapetama nakon ključne reči extends, npr. public interface Moj. Interfejs extends Tvoj. Interfejs, Nas. Interfejs{. . . } ● ● ● Sada Moj. Interfejs nasleđuje sve metode i konstante članove interfejsa Tvoj. Interfejs i Nas. Interfejs To se naziva višestrukim nasleđivanjem U Javi, klase ne podržavaju, a interfejsi podržavaju višestruko nasleđivanje
Interfejsi i višestruko nasleđivanje ● ● Potrebna je pažnja prilikom korišćenja ove mogućnosti. Ako 2 ili više super-interfejsa deklarišu metod sa istim potpisom, taj metod mora imati isti povratni tip u svim interfejsima koji ga deklarišu. Ako ne, kompajler će prijaviti grešku. Ovo zbog toga što bi bilo nemoguće da klasa implementira oba metoda pošto imaju isti potpis. Ako je metod deklarisan identično u svim interfejsima koji ga deklarišu, onda će jedna definicija u klasi zadovoljiti sve interfejse Kao što je rečeno ranije, svaki metod u klasi mora imati jednistven potpis, a povratni tip nije deo toga
Korišćenje interfejsa ● ● ● Za šta treba koristiti interfejse? Interfejs koji deklariše metode definiše standardni skup operacija. Razne klase mogu dodati takav standardni interfejs implementirajući ga. Tako objekti raznih klasa mogu da dele zajednički skup operacija. Naravno, data operacija u jednoj klasi može biti implementirana posve različito od načina na koji je implementirana u drugoj klasi. Ali, način na koji se poziva operacija je isti za objekte svih klasa koje implementiraju interfejs. Najvažnija upotreba interfejsa: pošto interfejs definiše tip, možemo izvršiti polimorfizam kroz skup klasa koje implementiraju isti interfejs. To je jako korisno. Pogledajmo
Interfejsi i polimorfizam ● ● Nije moguće kreirati objekte tipa interfejsa, ali moguće je kreirati promenljivu tipa interfejsa. Npr. Konstante konst = null; // promenljiva tipa interfejsa Konstante Ako ne možemo kreirati objekte tipa Konstante, kakva korist od toga? Koristimo tu promenljivu za smeštanje reference na objekat proizvoljne klase koja implementira interfejs Konstante. To znači da ovu promenljivu možemo koristiti kako bismo polimorfno pozivali metode deklarisane u interfejsu Konstante Videćemo kako to izgleda na pogodnom primeru koji se odnosi na kućnu audio/video opremu i daljinsko upravljanje
Pogodan primer – interfejsi i polimorfizam ● ● TV, hi-fi, VCR, DVD player i svaki od njih ima svoj daljinski upravljač. Svi daljinski upravljači verovatno imaju neki zajednički podskup dugmadi – power on/off, volume up, volume down, mute, itd. Želimo ”fancy” univerzalni daljinski upravljač – vrstu jedinstvene definicije daljinskog upravljača koji će odgovarati svoj opremi Univerzalni daljinski ima mnoštvo sličnosti sa interfejsom. Sam po sebi, univerzalni daljinski ne radi ništa. On definiše skup dugmadi za standardne operacije, ali operacija za svako dugme mora biti specifično programirana da odgovara svakoj vrsti uređaja kojim želimo da upravljamo
Primer – univerzalni daljinski ● ● ● TV, VCR, DVD itd. možemo predstaviti klasama, a svaka od njih će koristiti isti interfejs daljinskog upravljanja – tj. skup dugmadi – ali svaka na drugačiji način Remote. Control. java public interface Remote. Control{. . . } U interfejsu ne postoji definicija nijednog metoda Metodi deklarisani u interfejsu su uvek apstraktni po definiciji, a takodje su i public. Sada, proizvoljna klasa koja zahteva korišćenje funkcionalnosti obezbeđene Remote. Control interfejsom samo ima da deklariše da implementira Remote. Control interfejs i da uključi definicije za svaki od metoda iz interfejsa
Primer – univerzalni daljinski ● ● ● TV. java import static java. lang. Math. max; import static java. lang. Math. min; public class TV implements Remote. Control {. . . } Ova klasa implementira sve metode deklarisane u interfejsu Remote. Control, i svaki metod ispisuje poruku tako da znamo kada je pozvan. VCR. java Klasa VCR takođe implementira Remote. Control Naravno, možemo nastaviti i definisati klase za druge vrste uređaja koje koriste daljinski, ali ove dve su dovoljne za demonstraciju principa
Primer – univerzalni daljinski ● ● ● Test. Remote. Control. java import static java. lang. Math. random; public class Test. Remote. Control{. . . } Ovo je klasa koja operiše i TV i VCR objektima preko promenljive tipa Remote. Control Promenljiva remote je tipa Remote. Control pa je možemo koristiti za smeštanje reference na objekat proizvoljne klase koja implementira Remote. Control interfejs Unutar for-petlje kreiramo na slučajan način TV ili VCR objekat Pošto se tip objekta određuje u vreme izvršavanja i na slučajan način, izlaz iz programa demonstrira polimorfizam u akciji kroz promenljivu tipa interfejsa
Korišćenje više interfejsa ● ● ● Remote. Control objekat u prethodnom primeru može se koristiti za poziv samo onih metoda koji su deklarisani u interfejsu. Ako klasa implementira neki drugi interfejs osim Remote. Control, onda bi za poziv metoda deklarisanih u tom interfejsu trebalo ili koristiti promenljivu tipa tog interfejsa za čuvanje reference na objekat ili kastovati referencu na objekat u tip njegove stvarne klase Neka je klasa definisana sa: public class Moja. Klasa implements Remote. Control, Absolute. Control{ // Definicija klase koja uključuje metode iz oba interfejsa }
Korišćenje više interfejsa ● ● ● Pošto klasa implementira Remote. Control i Absolute. Control, možemo smestiti objekat tipa Moja. Klasa u promenljivu bilo kog od tipova 2 interfejsa, npr. Absolute. Control ac = new Moja. Klasa(); Sada možemo koristiti promenljivu ac za pozivanje metoda deklarisanih u Absolute. Control interfejsu. Međutim, ne možemo pozivati metode deklarisane u Remote. Control interfejsu koristeći ac, čak i ako referenca na objekat koju čuva ima ove metode. Jedna mogućnost je kastovati referencu u njen originalni klasni tip: ((Moja. Klasa)ac). power. On. Off();
Korišćenje više interfejsa ● ● ● Pošto kastujemo referencu u tip Moja. Klasa, možemo zvati proizvoljan metod definisan u toj klasi. Međutim, ovako ne možemo dobiti polimorfno ponašanje. Kompajler će odrediti koji metod se poziva u vreme kompajliranja Da bismo pozivali metode iz Remote. Control polimorfno, moraćemo da imamo referencu sačuvanu kao taj tip (Remote. Control). Znajući da je objekat tipa klase koja implementira Remote. Control interfejs, možemo od reference smeštene u ac dobiti referencu tipa Remote. Control: if(ac instanceof Remote. Control) ((Remote. Control)ac). mute(); Čak i ako interfejsi Remote. Control i Absolute. Control nisu ni u kakvoj vezi, možemo kastovati referencu iz ac u tip Remote. Control. To je moguće jer je objekat referisan pomoću ac zapravo tipa Moja. Klasa, koji implementira oba interfejsa.
Parametri metoda tipa interfejsa ● ● ● Parametar metoda može biti tipa interfejsa. To specijalno znači da se kao argument može proslediti objekat proizvoljne klase koja implementira taj interfejs Zadavanjem da je tip parametra interfejs, nagoveštavamo da je metod zainteresovan samo za metode interfejsa. Sve dok je objekat tipa koji implementira te metode, on je prihvatljiv kao argument. Ova tehnika deklarisanja parametra interfejsnog tipa se intenzivno koristi unutar biblioteke klasa. Klase String, String. Builder i String. Buffer implementiraju Char. Sequence interfejs, a postoji mnoštvo njihovih metoda koji imaju parametre tipa ovog interfejsa
0d4b9e6364ede5b9ebaa709a20d7cc5f.ppt