You are on page 1of 376

SADR Z AJ

Predgovor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 Osnove Jave . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.1 Osnovni koncepti 1 1.2 Naredbe 14 1.3 Metodi 30 1.4 Uvod u klase 42 Klase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.1 Klase i objekti 54 2.2 Promenljive klasnog tipa 57 2.3 Konstrukcija i inicijalizacija objekata 62 2.4 Uklanjanje objekata 68 2.5 Skrivanje podataka i enkapsulacija 69 2.6 Slubena re c this 73

iii 1

53

Nizovi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.1 Jednodimenzionalni nizovi 77 3.2 Dvodimenzionalni nizovi 93 3.3 Dinami cki nizovi 101

77

Nasledivanje klasa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113


4.1 Osnovni pojmovi 113 4.2 Hijerarhija klasa 118 4.3 Slubena re c super 125 4.4 Klasa Object 130 4.5 Polimorzam 134

Posebne klase i interfejsi . . . . . . . . . . . . . . . . . . . . . . . . . . 139 5.1 Nabrojivi tipovi 140 5.2 Apstraktne klase 144 5.3 Interfejsi 149 5.4 Ugnjedene klase 154 Gra cko programiranje . . . . . . . . . . . . . . . . . . . . . . . . . . 163 6.1 Gra cki programi 163 6.2 Gra cki elementi 166 6.3 Denisanje gra ckih komponenti 181 6.4 Klase za crtanje 184 6.5 Rukovanje dogadajima 194 Programske greke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207 7.1 Ispravni i robustni programi 207 7.2 Izuzeci 211 7.3 Postupanje sa izuzecima 220 7.4 Denisanje klasa izuzetaka 225

ii

S ADR AJ

Programski ulaz i izlaz . . . . . . . . . . . . . . . . . . . . . . . . . . . 229 8.1 Tokovi podataka 230 8.2 Datoteke 241 8.3 Imena datoteka
246

Programske niti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259


9.1 Osnovni pojmovi 259 9.2 Zadaci i niti za paralelno izvravanje 261 9.3 Sinhronizacija niti 271 9.4 Kooperacija niti 284

10 Mreno programiranje . . . . . . . . . . . . . . . . . . . . . . . . . . . 289 10.1 Komunikacija ra cunara u mrei 289 10.2 Veb programiranje 291 10.3 Klijent-server programiranje 297 10.4 Vienitno mreno programiranje 307 11 Generi cko programiranje . . . . . . . . . . . . . . . . . . . . . . . . . 321 11.1 Uvod 321 11.2 Sistem kolekcija u Javi 323 11.3 Denisanje generi ckih klasa i metoda 349 11.4 Ograni cenja za parametar tipa 355 Literatura . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365 Indeks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 367

Predgovor
Programski jezik Java je stekao veliku popularnost zbog svoje elegancije i savremene koncepcije. Programi napisani u Javi mogu se zato na ci u mobilnim uredajima, Internet stranama, multimedijalnim servisima, distribui ranim informacionim sistemima i mnogim drugim okruenjima ljudskog delovanja. Cilj ove knjige je da programerima pomogne da to lake nau ce Javu kako bi u svakodnevnom radu mogli da koriste mo can i moderan alat. Java je objektno orijentisan programski jezik, stoga se u prvom delu ove knjige govori o bitnim konceptima objektno orijentisanog programiranja, kao i o na cinu na koji su ti koncepti realizovani u Javi. Java je i programski jezik opte namene koji se koristi za reavanje zadataka iz razli citih oblasti primene. Programi napisani u Javi mogu biti obi cni tekstualni ili gra cki programi, ali i sloenije aplikacije zasnovane na principima mrenog programiranja, vienitnog programiranja, generi ckog programiranja i mnogih drugih posebnih tehnika. Naglasak u ovoj knjizi je uglavnom na ovim naprednijim mogu cnostima Jave o kojima se govori u drugom delu knjige. Ali zbog toga se od c italaca o cekuje odredeno predznanje o osnovnim konceptima programira nja. Speci cno, pretpostavlja se da c itaoci dobro poznaju osnovne elemente programiranja kao to su promenljive i tipovi podataka. S tim u vezi se podrazumeva poznavanje osnovnih upravlja ckih programskih konstrukcija u koje spadaju naredbe dodele vrednosti promenljivim, naredbe grananja i naredbe ponavljanja. Na kraju, neophodno je i poznavanje na cina za grupisanje ovih prostih upravlja ckih konstrukcija u sloenije programske strukture, od kojih su najzna cajniji potprogrami (obi cni i rekurzivni). Kratko re ceno, od c italaca se o cekuje da su ranije koristili neki programski jezik i da su solidno ovladali proceduralnim na cinom programiranja. Obim i sadraj ove knjige su prilagodeni nastavnom planu i programu od govaraju ceg predmeta na Fakultetu za informatiku i ra cunarstvo Univerziteta Singidunum u Beogradu. Tekst je propra cen velikim brojem slika i uradenih primera kojima se konkretno ilustruju novo uvedeni pojmovi. Primeri su birani tako da budu jednostavni, ali i da budu to realisti cniji i interesantniji.

iv

P REDGOVOR

Njihova dodatna svrha je da poslue kao po cetna ta cka za eksperimentisanje i dublje samostalno u cenje. A kao to je to ve c uobi cajeno za ra cunarske knjige, za prikazivanje programskog teksta se koristi font sa znakovima iste irine. Zahvalnost. Moje veliko zadovoljstvo je to mogu da izrazim najdublju zahvalnost svima koji su mi pomogli u pripremi ove knjige. Tu bih posebno izdvojio kolege i studente koji su svojim sugestijama u cinili da knjiga bude bolja. Veoma bih bio zahvalan i c itaocima na njihovom miljenju o knjizi. Sve primedbe i pronadene greke (kao i pohvale) mogu se poslati na adresu dzivkovic@singidunum.ac.rs.
D EJAN IVKOVI C Beograd, Srbija novembar 2010.

G LAVA 1

O SNOVE J AVE

U ovoj knjizi se uglavnom govori o naprednijim mogu cnostima Jave i zbog toga se od c italaca o cekuje odredeno predznanje iz programiranja. To zna ci da se podrazumeva da c itaoci mogu bez velikih teko ca razumeti proceduralni aspekt objektno orijentisanog jezika Java. To je upravo predmet ovog uvodnog poglavlja koje sadri samo kratak pregled osnovnih mogu cnosti jezika Java. Treba ipak imati u vidu da te osnove predstavljaju neophodan temelj za izu cavanje sloenijih koncepata Java programiranja.

1.1 Osnovni koncepti


Kada se govori o programskom jeziku Java, treba ista ci da formalna specikacija Jave obuhvata dve relativno nezavisne celine: opis samog programskskog jezika Java i opis ktivnog ra cunara (platforme) za izvravanje programa napisanih u Javi. Opis programskog jezika Java ne razlikuje se mnogo od sli cnih opisa za druge jezike opte namene i tome je posve cena cela ova knjiga. Novinu koju Java donosi je dakle opis ktivnog ra cunara, nazvanog Java virtuelna maina, za koji se Java programi prevode radi izvravanja. O tome se, vrlo kratko, govori u slede cem odeljku.

Java virtuelna maina


Svaki tip procesora ra cunara (Intel, PowerPC, Sparc, . . . ) ima poseban mainski jezik i moe izvriti neki program samo ako je taj program izraen na tom jeziku. Programi napisani na jeziku visokog nivoa kao to su Java, C, C++, C#, Pascal i tako dalje, ne mogu se direktno izvriti na ra cunaru. Takvi programi se moraju najpre prevesti na mainski jezik odgovaraju ceg procesora ra cunara. Ovo prevodenje programa na jeziku visokog nivoa u ekvivalentan

1. O SNOVE J AVE

program na mainskom jeziku obavljaju specijalni programi koji se zovu prevodioci (ili kompajleri). U slu caju programskog jezika Java se koristi pristup koji je druga ciji od uobi cajenog prevodenja Java program na mainski jezik stvarnog procesora ra cunara. Naime, programi napisani u Javi se i dalje prevode na mainski jezik, ali taj mainski jezik nije jezik nekog stvarnog procesora nego izmiljenog ra cunara koji se naziva Java virtuelna maina (skra ceno JVM ). Mainski jezik Java virtuelne maine se naziva Java bajtkod. Prema tome, Java program se prevodi u Java bajtkod koji se ne moe direktno izvriti na stvarnom ra cunaru. Za izvravanje Java programa prevedenog u Java bajtkod potreban je dodatni program koji interpretira instrukcije Java bajtkoda na stvarnom ra cunaru. Ovaj interpretator Java bajtkoda c esto se u argonu pogreno naziva istim imenom Java virtuelna maina, to ponekad dovodi do zabune jer treba razlikovati opis ktivnog ra cunara od specijalnog programa stvarnog ra cunara koji simulira instrukcije tog ktivnog ra cunara na stvarnom ra cunaru. ema izvravanje Java programa na razli citim platformama prikazana je na slici 1.1.
Java interpretator za Intel Java program Java prevodilac Java bajtkod Java interpretator za PowerPC Java interpretator za Sparc

Slika 1.1: Izvravanje Java programa na razli citim platformama. Opis Java virtuelne maine nije predmet ove knjige, jer za potrebe izu cavanja programskog jezika Java nije potrebno znati mainski jezik ra cunara na kome se Java program izvrava. Detaljnije poznavanje Java virtuelne maine potrebno je specijalnim stru cnjacima koji piu prevodioce ili interpretatore za Javu. Obi cnim programerima koji koriste programski jezik Java za pisanje svojih programa dovoljno je samo poznavanje opte eme na slici 1.1 koja se primenjuje za izvravanje Java programa. Napomenimo jo da programski jezik Java u principu nema nikakve veze sa Java virtuelnom mainom. Programi napisani u Javi bi se svakako mogli prevoditi u mainski jezik stvarnog ra cunara. Ali i obrnuto, programi na drugom programskom jeziku visokog nivoa bi se mogli prevoditi u Java bajtkod.

1.1. Osnovni koncepti

Ipak, kombinacija Java jezika i Java virtuelne maine obezbeduje programi ranje na modernom, objektno orijentisanom jeziku i istovremeno bezbedno izvravanje napisanih programa na razli citim tipovima ra cunara.

Razvoj programa
Razvoj programa u svim programskim jezicima obuhvata tri glavna koraka: unoenje, prevodenje i pokretanje (testiranje) programa. Ovaj c esto mu kotrpan proces moe se u slu caju Jave obavljati u okviru dva osnovna razvojna okruenja: tekstualnog i gra ckog. Dok je tekstualno razvojno okruenje za Javu relativno standardno (konzolni prozor za Unix, DOS prozor za Windows ili sli cno komandno okruenje za druge operativne sisteme), programerima su na raspolaganju mnoga gra cka razvojna okruenja, kako besplatna tako i komercijalna (NetBeans, Eclipse, DrJava, BlueJ, JCreator i tako dalje). Medu ovim okruenjima se ne moe izdvojiti jedno koje je najbolje, jer svako ima svoje prednosti i mane, a i njihov izbor c esto zavisi od li cnog ukusa. Zbog toga se u nastavku ograni cavamo na najjednostavnije tekstualno okruenje za Windows. Ovim se ne gubi mnogo na kompletnosti, jer se na kraju sve svodi na izvravanje odredenih komandi operativnog sistema. Pored toga, sva okruenja rade na sli can na cin i nije teko pre ci na drugo okruenje ukoliko se poznaje bilo koje. Program u Javi se na logi ckom nivou sastoji od jedne ili vie klasa. Izvorni tekst svake klase se smeta u posebnu datoteku c iji naziv mora po ceti imenom klase koju sadri, a ekstenzija naziva te datoteke mora biti java.1 Na primer, slede ci jednostavan program se sastoji od samo jedne klase Zdravo.
public class Zdravo { public static void main(String[] args) { System.out.println("Zdravo narode!"); } }

Prema tome, koriste ci bilo koji editor, tekst klase (programa) Zdravo treba uneti u ra cunar i sa cuvati taj izvorni tekst u datoteku pod obaveznim nazivom Zdravo.java. Izvravanje svakog programa u Javi po cinje od izvravanja metoda (funkcije) u klasi sa slubenim imenom main. U prethodnom primeru klase Zdravo, taj metod se sastoji od samo jedne naredbe kojom se prikazuje odredena po ruka na ekranu.
1 Iako neki Java prevodioci doputaju smetanje teksta vie klasa u istu datoteku, takva praksa se ne preporu cuje.

1. O SNOVE J AVE

Ali pre izvravanja, bilo koji program u Javi se mora prevesti u odgovaraju ci bajtkod. Svaka klasa u programu se moe prevoditi nezavisno od drugih klasa programa. Prevodenje jedne klase se obavlja Java prevodiocem koji se pokre ce komandom javac. Na primer, komanda za prevodenje klase Zdravo u datoteci Zdravo.java je:
javac Zdravo.java

Rezultat prevodenja klase Zdravo je njen bajtkod koji se smeta u datoteku pod nazivom Zdravo.class. Primetimo da naziv ove datoteke po cinje imenom klase c iji bajtkod sadri, a zavrava se standardnom ekstenzijom class. Na kraju, izvravanje (interpretiranje) dobijenog bajtkoda neke klase obavlja se Java interpretatorom koji se pokre ce komandom java. Na primer, komanda za izvravanje bajtkoda klase Zdravo u datoteci Zdravo.class je:
java Zdravo

Primetimo ovde da je argument Java interpretatora samo klasa c iji bajtkod treba interpretirati, a ne datoteka Zdravo.class koja sadri taj bajtkod. Naravno, interpretiranje bajtkoda klase Zdravo po cinje od metoda main() u toj klasi, a efekat toga je izvravanje jedine naredbe u tom metodu kojom se prikazuje navedena poruka na ekranu. U prethodnom primeru se program sastojao od samo jedne klase, ali se sli can postupak primenjuje i za izvravanje programa koji se sastoji od vie klasa. Prakti cno jedina razlika je to se sve klase moraju pojedina cno prevesti u Java bajtkod. Nakon toga, izvravanje celog programa izvodi se interpretiranjem bajtkoda logi cki po cetne klase programa. A time c e se onda prema logici programa u odgovaraju cem trenutku interpretirati i bajtkodovi ostalih klasa programa. Istaknimo jo jednu mogu cnost koja se c esto koristi radi testiranja programa. Naime, nema nikakve prepreke da se u vie klasa koje c ine program nalazi metod main(). Onda se postavlja pitanje odakle se po cinje sa izvravanjem takvog programa? Prosto, to se odreduje u komandi za izvravanje pro grama navodenjem one klase kao argumenta od c ijeg metoda main() treba po ceti izvravanje.

Komentari
Komentar je tekst na prirodnom jeziku u programu koji slui za objanjenje delova programa drugim programerima. Takva konstrukcija se zanemaruje od strane Java prevodioca i potpuno se preska ce prilikom prevodenja programa. Iako za Java prevodilac komentari kao da ne postoje, to ne zna ci da

1.1. Osnovni koncepti

su oni nevani. Bez komentara je vrlo teko razumeti kako radi neki iole sloeniji program. Zbog toga se pisanje dobrih i kratkih komentara u programu nikako ne sme olako shvatiti. U Javi se mogu koristiti tri vrste komentara. Prva vrsta komentara su kratki komentari koji se piu u jednom redu teksta. Njihov po cetak u nekom redu se ozna cava simbolom // i obuhvataju tekst do kraja tog reda. Na primer:
// Prikazivanje ocena svih studenata int i = 0; // inicijalizacija broja studenata

Druga vrsta komentara sadri tekst koji se prostire u vie redova. Oni po cinju simbolom /*, obuhvataju tekst preko proizvoljnog broja redova i zavravaju se simbolom */. Ceo tekst izmedu simbola /* i */ potpuno se zanemaruje od strane Java prevodioca. Na primer:
/* * Uspostavljanje veze sa veb serverom. * Ako uspostavljanje veze ne uspe nakon tri pokuaja, * program se prekida jer nema smisla nastaviti obradu. */

Tre ca vrsta komentara su specijalni slu caj druge vrste i po cinju simbolom /**, a ne /*, ali se isto zavravaju simbolom */. Ovi komentari se nazivaju dokumentacioni komentari jer slue za pisanje dokumentacije o klasi direktno unutar njenog izvornog teksta. Pomo cni program javadoc izdvaja ove komentare u poseban hipertekst dokument koji se moe lako c itati bilo kojim veb pretraiva cem. Pored obi cnog teksta, dokumentacioni komentari mogu sadrati i HTML tagove i specijalne re ci koje po cinju znakom @. Na primer:
/** * Prenoenje datoteke na veb server. * * @param datoteka Datoteka koja se prenosi. * @return <tt>true</tt> ako je prenos bio uspean, <tt>false</tt> ako je prenos bio neuspean. * c * @author Dejan ivkovi */

Potpun opis sintakse dokumentacionih komentara nije svakako neophodan za u cenje Jave. Zato se c itaocima preputa da, kada im zatrebaju detaljnija objanjenja u vezi sa dokumentacionim komentarima, o tome sami pro citaju u zvani cnoj dokumentaciji za pomo cni program javadoc.

Slobodan format programa


Java je programski jezik slobodnog formata. To zna ci da ne postoje sintaksna ograni cenja u odnosu na na cin na koji se pojedine sastavne konstrukcije

1. O SNOVE J AVE

piu unutar programa, odnosno izgled teksta programa je sasvim slobodan. Tako se, izmedu ostalog, tekst programa moe pisati od proizvoljnog mesta u redu, a ne od poc etka reda; naredbe se ne moraju pisati u pojedina cnim redovima, nego se vie njih moe nalaziti u istom redu; izmedu ci teksta programa moe biti proizvoljan broj razmaka, a ne re samo jedan; izmedu dva reda teksta programa moe biti proizvoljan broj praznih redova. Na primer, sa gledita Java prevodioca, prihvatljiv izgled klase Zdravo na strani 3 je i ovakav njen oblik:
public class Zdravo{public void main(String[] args) { System. ("Zdravo narode!" ) static out ; .println }}

Ali, naravno, ova ispravna verzija klase Zdravo je potpuno ne citljiva za ljude. Slobodan format jezika Java ne treba zloupotrebljavati, ve c to treba iskoristiti radi pisanja programa c ija vizuelna struktura jasno odraava njegovu logi cku strukturu. Pravila dobrog stila programiranja zato nalau da se pie jedna naredba u jednom redu, da se uvla cenjem i poravnavanjem redova ozna ci blok naredbi koje c ine jednu logi cku celinu, da imena kojima se ozna cavaju razni elementi program budu smislena i tako dalje. Slobodan format programa u Javi treba dakle iskoristiti da bi se struktura teksta programa u cinila to jasnijom za onoga ko pokuava da na osnovu tog teksta shvati kako program radi. Uz pridravanje ove preporuke i pisanjem dobrih komentara u klju cnim delovima programa, razumljivost komplikovanih programa se moe znatno poboljati. Laka razumljivost programa je vana zbor njegovog eventualnog kasnijeg modikovanja, jer tada treba ta cno razumeti kako program radi da bi se mogao ispravno izmeniti.

Tipovi podataka
Podaci kojima Java programi mogu da manipuliu dele se u dve glavne kategorije: 1. Primitivni (prosti) tipovi podataka su neposredno raspoloivi tipovi podataka koji su unapred ugradeni u jezik.

1.1. Osnovni koncepti

2. Klasni tipovi podataka su novi tipovi podataka koje programer mora sm da denie. Primitivnim tipovima podataka u Javi obuhva ceni su osnovni podaci koji nisu objekti. Vrednosti primitivnih tipova podataka su atomi cni u smislu da se ne mogu deliti na sastavne delove. Tu spadaju celi brojevi, realni brojevi, alfabetski znakovi i logi cke vrednosti. U tabeli 1.1 se nalazi spisak svih primitivnih tipova sa njihovim osnovnim karakteristikama. Tip podataka
byte short int long float double char boolean void

Veli cina vrednosti 1 bajt 2 bajta 4 bajta 8 bajtova 4 bajta 8 bajtova 2 bajta 1 bit

Opseg vrednosti [128, 127] [32768, 32767] [232 , 232 1] [264 , 264 1] 1038 10308

Tabela 1.1: Primitivni tipovi podataka. Tipovi podataka byte, short, int i long slue za rad sa celim brojevima (brojevima bez decimala kao to su 17, 1234 i 0) i razlikuju se po veli cini memorijske lokacije koja se koristi za binarni zapis celih brojeva. Tipovi podataka float i double slue za predstavljanje realnih brojeva (brojeva sa decimalama kao to su 1.69, 23.678 i 5.0). Ova dva tipa se razlikuju po veli cini memorijske lokacije koja se koristi za binarni zapis realnih brojeva, ali i po ta cnosti tog zapisa (tj. maksimalnom broju cifara realnih brojeva). Tip podataka char sadri pojedina cne alfabetske znakove kao to su mala i velika slova (A, a, B, b, . . . ), cifre (0, 1, . . . ), znakovi interpunkcije (*, ?, . . . ) i neki specijalni znakovi (za razmak, za novi red, za tabulator, . . . ). Znakovi tipa char predstavljaju se u binarnom obliku prema standardu Unicode, koji je izabran zbog mogu cnosti predstavljanje pisama svih jezika na svetu, odnosno zbog internacionalizacije koja je uvek bila medu osnovnim ciljevima Jave. Tip podataka boolean slui za predstavljanje samo dve logi cke vrednosti: ta cno (true) i neta cno (false). Logi cke vrednosti u programima se naj ce ce

1. O SNOVE J AVE

dobijaju kao rezultat izra cunavanja relacijskih operacija. Najzad, deveti primitivni tip podataka void je na naki na cin degenerativan jer ne sadri nijednu vrednost. On je analogan praznom skupu u matematici i slui u nekim sintaksnim konstrukcijama za formalno ozna cavanje tipa podataka bez vrednosti. Svi primitivni tipovi podataka imaju ta cno odredenu veli cinu memorijske lokacije u bajtovima za zapisivanje njihovih vrednosti. To zna ci da primitivni tipovi podataka imaju ta cno denisan opseg vrednosti koje im pripadaju. Na primer, vrednosti koje pripadaju tipu short su svi celi brojevi od 32768 do 32767. Osnovna osobina primitivnih tipova podataka je da se njima predstavljaju proste vrednosti (brojevi, znakovi i logi cke vrednosti) koje se ne mogu dalje deliti. Ali za svaki tip podatka su denisane i razli cite operacije koje se mogu primenjivati nad vrednostima odredenog tipa. Na primer, za vrednosti numeri ckih tipova (celi i realni brojevi) denisane su uobi cajene aritmeti cke operacije sabiranja, oduzimanja, mnoenja i deljenja. Svi ostali tipovi podataka u Javi pripadaju kategoriji klasnih tipova podataka kojima se predstavljaju klase objekata. Objekti su sloeni podaci po tome to se grade od sastavnih delova kojima se moe nezavisno manipulisati. O klasama i objektima bi ce mnogo vie re ci u nastavku knjige. Treba na kraju primetiti da stringovi (tj. nizovi znakova), koji su c esto kori cena vrsta podataka u programiranju, nemaju odgovaraju ci primitivni tip u Javi. Skup stringova se u Javi smatra klasnim tipom, odnosno stringovi se podrazumevaju da su objekti koji pripadaju unapred denisanoj klasi String.

Promenljive
Promenljiva je simboli cko ime za neku memorijsku lokaciju koja slui za smetanje podataka u programu. Sve promenljive se moraju deklarisati pre nego to se mogu koristiti u Java programu. U najprostijem obliku, deklaracija jedne promenljive se sastoji od navodenja tipa promenljive, kojim se odreduje tip podataka koje promenljiva sadri, i u produetku imena promenljive. Na primer:
int godina; long n; float a1; double obim;

U ovom primeru su deklarisane promenljive godina, n, a1 i obim odgovaraju ceg tipa koji je naveden ispred imena svake promenljive. Efekat deklaracije jedne promenljive je rezervisanje potrebnog memorijskog prostora u

1.1. Osnovni koncepti

programu prema tipu promenljive i uspostavljanje veze izmedu ckog simboli imena promenljive i njenog rezervisanog prostora. Obratite panju na to da promenljiva moe imati razli cite vrednosti tokom izvravanja programa, ali sve te vrednosti moraju biti istog tipa onog navedenog u deklaraciji promenljive. U prethodnom primeru, vrednosti recimo promenljive obim mogu biti samo realni brojevi tipa double nikad celi brojevi, znakovi, logi cke vrednosti ili vrednosti nekog klasnog tipa. U Javi se promenljiva ne moe koristiti u programu ukoliko ne sadri neku vrednost. Kako po cetno rezervisanje memorijskog prostora za promenljivu ne uklju cuje nikakvo dodeljivanje vrednosti promenljivoj, prosta deklaracija promenljive obi cno se kombinuje sa njenom inicijalizacijom da bi se promenljiva mogla odmah koristiti. Na primer:
int i = 0; double x = 3.45; String imePrezime = "Pera Peri c";

U ovom primeru su deklarisane promenljive i, x i imePrezime odgovaraju ceg tipa, ali im se dodatno dodeljuju po cetne vrednosti navedene iza znaka = u njihovim deklaracijama. Deklaracija promenljivih moe biti, u stvari, malo sloenija od gore prikazanih oblika. Naime, iza tipa promenljive se moe pisati lista vie promenljivih (sa ili bez po cetnih vrednosti) koje su odvojene zapetama. Time se prosto deklarie (i inicijalizuje) vie promenljivih istog tipa. Na primer:
int i, j, k = 32; long n = 0L; boolean indikator = false; String ime, srednjeIme, prezime; float a = 3.4f, b, c = 0.1234f; double visina, irina;

Imena promenljivih (kao i imena drugih programskih elemenata) u Javi moraju po ceti slovom, a iza po cetnog slova mogu se nalaziti slova ili cifre.2 Duina imena promenljivih je prakti cno neograni cena. Znakovi kao to su + ili @ ne mogu se koristiti unutar imena promenljive, niti se mogu koristiti razmaci. Velika i mala slova se razlikuju u imenima promenljivih tako da se visina, Visina, VISINA i viSiNa smatraju razli citim imenima. Izvesne re ci u Javi imaju specijalnu ulogu i zato se one ne mogu korisiti za imena promenljivih. Ove slubene (ili rezervisane) re ci su u primerima u ovoj knjizi naglaene podebljanim slovima.
2 Pojmovi slovo i cifra su mnogo iri u Javi. Tako sva slova obuhvataju ne samo velika i

mala slova engleske azbuke, nego i bilo koji Unicode znak koji ozna cava slovo u nekom jeziku. Zato se, recimo, i naa slova c , c,,d, mogu koristiti u imenima.

10

1. O SNOVE J AVE

Pored ovih formalnih pravila za davanje ispravnih imena promenljivim, u Java programima se koristi i jedna neformalna konvencija radi poboljanja razumljivosti programa. Naime, imena promenljivih se piu samo malim slovima, na primer visina. Ali ako se ime promenljive sastoji od nekoliko re ci, recimo brojStudenata, onda se svaka re c od druge pie sa po cetnim velikim slovom.

Konstante
Jedna promenljiva moe sadrati promenljive vrednosti istog tipa tokom izvravanja programa. U izvesnim slu cajevima, medutim, vrednost neke pro menljive u programu ne treba da se menja nakon dodele po cetne vrednosti toj promenljivoj. U Javi se u naredbi deklaracije promenljive moe dodati slubena re c final kako bi se obezbedilo da se promenjiva ne moe promeniti nakon to se inicijalizuje. Na primer, iza deklaracije
final int MAX_BROJ_POENA = 100;

svaki pokuaj promene vrednosti promenljive MAX_BROJ_POENA proizvodi sintaksnu greku. Ove nepromenljive promenljive se nazivaju konstante, jer njihove vrednosti ostaju konstantne za sve vreme izvravanja programa. Obratite panju na konvenciju u Javi za pisanje imena konstanti: njihova imena se sastoje samo od velikih slova, a za eventualno razdvajanje re ci slui donja crta. Ovaj stil se koristi i u standardnim klasama Jave u kojima su denisane mnoge konstante. Tako, na primer, u klasi Math se nalazi konstanta PI koja sadri vrednost broja , ili u klasi Integer se nalazi konstanta MIN_VALUE koja sadri minimalnu vrednost tipa int.

Izrazi
Izrazi su, pojednostavljeno re ceno, formule na osnovu kojih se izra cunava neka vrednost. Primer jednog izraza u Javi je:
2 * r * Math.PI

Bitna karakteristika izraza je to to se tokom izvravanja programa svaki izraz izra cunava i kao rezultat se dobija ta cno jedna vrednost koja predstavlja vrednost izraza. Ova vrednost izraza se moe dodeliti nekoj promenljivoj, koristiti u drugim naredbama, koristiti kao argument u pozivima metoda, ili kombinovati sa drugim vrednostima u gradenju sloenijeg izraza. Izrazi se dele na proste i sloene. Prost izraz moe biti literal (tj. bukvalna vrednost nekog tipa kao to je 17, 0.23, true ili A), promenljiva ili

1.1. Osnovni koncepti

11

poziv metoda. Vrednost prostog izraza je sama literalna vrednost, vrednost promenljive ili rezultat poziva metoda. Sloeni izrazi se grade kombinovanjem prostih izraza sa dozvoljenim operatorima (aritmeti ckim, relacijskim i drugim). Izra cunavanje vrednosti sloenog izraza odvija se primenom operacija koje ozna cavaju operatori na vrednosti prostijih izraza koji se nalaze u sloenom izrazu. Izraz 2 * r * Math.PI, na primer, izra cunava se tako to se vrednost literala 2 (koja je broj 2) mnoi sa aktuelnom vredno cu promenljive r, a zatim se ovaj medurezultat mnoi sa vredno cu konstante Math.PI (koja je 3.14 ) da bi se dobila kona cna vrednost izraza. Operatori. Operatori koji slue za gradnju sloenih izraza u Javi ozna cavaju uobi cajene operacije nad odgovaraju cim tipovima podataka.3 Nepotpun spisak raspoloivih operatora moe se podeliti u nekoliko grupa: Aritmeti cki operatori: +, -, *, /, %, ++, - Logi cki operatori: &&, ||, ! Relacijski operatori: <, >, <=, >=, ==, != Operatori nad bitovima: &, |, ~, ^ Operatori pomeranja: <<, >>, >>> Operatori dodele: =, +=, -=, *=, /=, %= Operator izbora: ?: Opis svih ovih operatora nije teak, ali je preobiman i verovatno suvian, jer se pretpostavlja da c itaoci poznaju makar aritmeti cke, logi cke i relacijske operatore. Ukoliko to nije slu caj, detaljnija objanjenja c itaoci mogu potraiti u svakoj uvodnoj knjizi za Javu (videti spisak literature na kraju ove knjige). Ukoliko se u sloenom izrazu nalazi vie operatora, redosled primene tih operatora radi izra cunavanja izraza odreduje se na osnovu prioriteta svakog operatora. Izraz, na primer,
x + y * z

izra cunava se tako to se najpre izra cunava podizraz y * z, a zatim se njegov rezultat sabira sa x. To je tako zato to mnoenje predstavljeno operatorom * ima vii prioritet od sabiranja koje je predstavljeno operatorom +. Ako ovaj unapred denisan prioritet operatora nije odgovaraju ci, redosled izra cunavanja izraza moe se promeniti upotrebom zagrada. Na primer, kod izraza
3 Operatori i njihove oznake su skoro potpuno preuzeti iz jezika C (ili C++).

12

1. O SNOVE J AVE

(x + y) * z bi se najpre izra cunavao podizraz u zagradi x + y, a zatim bi se nje-

gov rezultat mnoio sa z. Ako se u izrazu nade vie operatora koji imaju isti prioritet, onda se redosled njihove primene radi izra cunavanja izraza odreduje na osnovu asoci jativnosti operatora. Asocijativnost ve cine operatora u Javi denisana je tako da se oni primenjuju sleva na desno. Operatori za sabiranje i oduzimanje, na primer, imaju isti prioritet i asocijativnost sleva na desno. Zato se izraz x + y - z izra cunava kao da stoji (x + y) - z. S druge strane, asocijativnost operatora dodele i unarnih operatora je zdesna na levo. Zato se izraz x = y = z++ izra cunava kao da stoji x = (y = (z++)). Unapred denisan prioritet i asocijativnost svih operatora u Javi moe se lako saznati iz zvani cne dokumentacije jezika. Medutim, pravila prioriteta i asocijativnosti operatora ne treba pamtiti napamet, jer se ona iaonako brzo zaboravljaju ili pomeaju sa sli cnim, ali ne i identi cnim, pravilima iz drugih programskih jezika. Umesto toga, poto se zagrade mogu slobodno koristiti, uvek kada moe do ci do zabune treba navesti zagrade da bi se eksplicitno nazna cio eljeni redosled primene operatora kod izra cunavanja izraza. Time se znatno pove cava razumljivost izraza, naro cito onih komplikovanijih, i smanjuje mogu cnost suptilnih greaka koje je teko otkriti. Konverzija tipa. Prilikom izra cunavanja izraza, pored prioriteta i asocijativnosti operatora, obra ca se panja i na to to da li se neki operator primenjuje na vrednosti istog tipa. Ako to nije slu caj, vrednost jednog tipa pretvara se u ekvivalentnu vrednost drugog tipa. Na primer, prilikom izra cunavanja izraza 17.4 + 10, najpre se ceo broj 10 pretvara u ekvivalentni realni broj 10.0, a zatim se izra cunava 17.4 + 10.0 i dobija realni broj 27.4. Ovaj postupak, koji se naziva konverzija tipa, obavlja se automatski ukoliko ne dolazi do gubitka podataka. Preciznije, medu primitivnim tipovima podataka dozvoljena je konverzija izmedu celih i realnih brojeva. Pored toga, kako svakom znaku tipa char odgovara jedan broj prema emi kodiranja Unicode, znakovi se mogu konvertovati u cele ili realne brojeve. U stvari, vrednosti logi ckog tipa boolean su jedine koje ne mogu u cestvovati ni u kakvoj konverziji u neki primitivni tip podataka ili iz njega.4 Primetimo da se mogu razlikovati dve osnovne vrste konverzija: proiruju ce i suavaju ce konverzije. Proiruju ca konverzija je ona kod koje dolazi do pretvaranja vrednosti manjeg tipa u ekvivalentnu vrednost ve ceg tipa.
4 To zna ci da se u Javi ne moe koristiti C-stil pisanja naredbi u obliku while (1) ... ili
if (a) ... gde je a celobrojna promenljiva.

1.1. Osnovni koncepti

13

Ovde odrednice manji ili ve ci za tip ozna cavaju manji ili ve ci skup (opseg) vrednosti koje pripadaju tipu. Na primer, ako se celobrojna vrednost tipa int dodeljuje promenljivoj tipa double ili se znak tipa char dodeljuje promenljivoj tipa int, onda se radi o proiruju coj konverziji iz tipa int u tip double, odnosno iz tipa char u tip int. U Javi se proiruju ca konverzija vri automatski kada je to potrebno i o tome obi cno ne treba voditi ra cuna u programu. Na slici 1.2 su prikazane proiruju ce konverzije primitivnih tipova koje su ispravne u Javi.
char

byte

short

int

long

float

double

Slika 1.2: Proiruju ce konverzije primitivnih tipova. Pune strelice na slici 1.2 ozna cavaju konverzije bez gubitka podataka. Isprekidane strelice ozna cavaju konverzije bez gubitka podataka, ali uz mogu c gubitak ta cnosti. Na primer, ceo broj 123456789 ima vie od 8 zna cajnih cifara koliko se najvie moe predstaviti tipom float. Zato kada se taj veliki ceo broj konvertuje u tip float, rezultuju ci realni broj je istog reda veli cine ali sa smanjenom ta cno cu:
int i = 123456789; float f = i; // f je 123456792.0

Suavaju ca konverzija je suprotna od proiruju ce vrednost ve ceg tipa se pretvara u manji tip. Suavaju ca konverzija nije uvek mogu ca: broj 17 tipa int ima smisla pretvoriti u istu vrednost tipa byte, ali broj 1700 tipa int ne moe se pretvoriti u tip byte, jer brojevi tipa byte pripadaju samo intervalu od 128 do 127. Zbog mogu ceg gubitka podataka, suavaju ca konverzija bez dodatnog zahteva proizvodi sintaksnu greku u programu, c ak i u slu caju kada vrednost koja se pretvara pripada zapravo manjem tipu:
int i = 17; byte b = i; // GREKA!

Ukoliko suavaju ca konverzija svakako ne dovodi do gubitka podataka, njeno obavezno izvravanje u izrazu moe se zahtevati operatorom ekspli-

14

1. O SNOVE J AVE

citne konverzije tipa (engl. type cast ). Oznaka tog operatora je ime ciljnog tipa podataka u zagradi, to se pie ispred vrednosti koju treba pretvoriti u navedeni ciljni tip. Na primer:
int i = 17; byte b = (byte) i; // eksplicitna konverzija 17 u tip byte i = (int) 45.678; // eksplicitna konverzija 45.678 u tip int

Eksplicitne konverzije primitivnih tipova naj ce ce se koriste izmedu realnih brojeva i celih brojeva. Jedna od takvih primena operatora eksplicitne konverzije jeste za suavaju cu konverziju realnih brojeva u cele brojeve. U tom slu caju se decimalni deo realnog broja prosto odbacuje. Tako, rezultat eksplicitne konverzije (int) 45.678 u poslednjoj naredbi prethodnog primera predstavlja ceo broj 45 koji se dodeljuje promenljivoj i. Primetimo da ovo nije isto to i zaokruivanje realnog broja na najblii ceo broj zaokruivanje realnih brojeva u tom smislu dobija se kao rezultat metoda Math.round(). Mada se proiruju ca konverzija celih brojeva u realne brojeve automatski izvrava, u nekim slu cajevima je potrebno eksplicitno zahtevati takvu konverziju. Jedan od tih slu cajeva je posledica efekta operacije deljenja za cele brojeve: rezultat celobrojnog deljena dva cela broja je isto ceo broj i eventualne decimale rezultata se odbacuju. Rezultat izraza 7 / 2, recimo, jeste ceo broj 3, a ne ta can broj 3.5 koji je realna vrednost. Ako je svakako potrebno dobiti ta can rezultat celobrojnog deljena, onda se deljenik (ili delilac) moe ekplicitno pretvoriti u realan broj. To c e izazvati automatsko pretvaranje i drugog operanda u realan broj, pa c e se deliti dva realna broja i dobiti ta can rezultat koji je realan broj. Na primer:
int m = 7; int n = 2; double x = (double) m / n;

// x = 3.5

Primetimo u ovom primeru da se na desnoj strani poslednje naredbe izraz


(double) n / m ispravno izra cunava kao da stoji ((double) n) / m, jer operator

eksplicitne konverzije ima vii prioritet u odnosu na operator deljenja. Da to nije slu caj, odnosno da operator deljenja ima vii prioritet, navedeni izraz bi se izra cunavao kao da stoji (double) (n / m), to bi dalo pogrean rezultat (dodue realan broj) 3.0.

1.2 Naredbe
Naredba je jedna komanda koju izvrava Java interpretator tokom izvravanja Java programa. Podrazumevani redosled izvravanja naredbi je onaj u

1.2. Naredbe

15

kojem su naredbe napisane u programu, mada u Javi postoje mnoge upravlja cke naredbe kojima se ovaj sekvencijalni redosled izvravanja moe promeniti. Naredbe u Javi mogu biti proste ili sloene. Proste naredbe obuhvataju naredbu dodele i naredbu deklaracije promenljive. Sloene naredbe slue za komponovanje prostih i sloenih naredbi radi denisanja komplikovanijih upravlja ckih struktura u programu. U sloene naredbe spadaju blok naredbi, naredbe grananja (if naredba, if-else naredba i switch naredba) i naredbe ponavljanja (while naredba, do-while naredba i for naredba).

Naredba dodele
Naredba dodele je osnovni na cin da se vrednost nekog izraza dodeli promenljivoj u programu. Glavni opti oblik naredbe dodele je:
promenljiva = izraz;

Efekat izvravanja naredbe dodele moe se podeliti u dva koraka: najpre se izra cunava vrednost izraza na desnoj strani znaka jednakosti, a zatim se ta vrednost dodeljuje promenljivoj na levoj strani znaka jednakosti. Na primer, izvravanje naredbe dodele
obim = 2 * r * Math.PI;

sastoji se od izra cunavanja izraza 2 * r * Math.PI (uzimaju ci aktuelnu vrednost promenljive r i mnoe ci je sa 2 3.14 . . .) i upisivanja rezultuju ce vrednosti u promenljivu obim. Obratite panju na zavrnu ta cku-zapetu kod naredbe dodele. Bez ta ckezapete se zapravo dobija operator dodele c ija oznaka je znak jednakosti i c iji rezultat je izra cunata vrednost izraza na desnoj strani znaka jednakosti (pored dodele te vrednosti promenljivoj na levoj strani znaka jednakosti). U stvari, bilo koji operator dodele moe se koristiti kao naredba dodele dodavanjem ta cke-zapete na kraju. (Ovo vai i za druge operatore koji proizvode sporedno dejstvo.) Na primer:
brojStudenata = 30; // dodela a += b; // kombinovana dodela n++; // inkrementiranje m--; // dekrementiranje

Naredba deklaracije promenljive


Sve promenljive se moraju deklarisati pre nego to se mogu koristiti u Java programu. Kako je Java striktno tipovan programski jezik, svaka promenljiva

16

1. O SNOVE J AVE

pored imena mora imati deklarisan i tip kojim se odreduje tip podataka koje promenljiva sadri. Opti oblik naredbe deklaracije promenljive je:
tip promenljiva;

ili
tip promenljiva = izraz;

Ovde tip moe biti jedan od primitivnih ili klasnih tipova, promenljiva predstavlja ime promenljive koja se deklarie, a izraz se izra cunava da bi se dodelila po cetna vrednost promenljivoj koja se deklarie. Budu ci da je o primerima deklarisanja promenljivih ve c bilo re ci na strani 8, ovde c emo se osvrnuti samo jo na neke detalje u vezi sa deklaracijama promenljivih. Pre svega, prethodni oblik naredbe deklaracije promenljive nije najoptiji jer moe po ceti slubenom re cju final. U tom slu caju se deklariu zapravo konstante o kojima se bilo re ci u odeljku 1.1. Za razliku od nekih programskih jezika kod kojih se deklaracije svih promenljivih moraju nalaziti na po cetku programskog koda, u Javi se deklaracije promenljivih mogu pisati na bilo kom mestu. Naravno, to vai uz potovanje pravila da svaka promenljiva mora biti deklarisana (i inicijalizovana) pre nego to se koristi u metodu. Promenjive se mogu deklarisati unutar metoda ili unutar klasa. Promenljive deklarisane unutar nekog metoda se nazivaju lokalne promenljive za taj metod. One postoje samo unutar metoda u kojem su deklarisane i potpuno su nedostupne izvan tog metoda. Za lokalne promenljive se c esto koristi obi can termin promenljive. To ne izaziva zabunu, jer kada se deklariu unutar neke klase radi opisa atributa objekata te klase, promenljive se nazivaju polja.

Blok naredbi
Blok naredbi (ili kra ce samo blok ) je niz od vie naredbi napisanih izmedu otvorene i zatvorene viti caste zagrade. Opti oblik bloka naredbi je:
{ naredba1 naredba2 . . . naredban }

Osnovna svrha bloka naredbi je samo da se niz bilo kakvih naredbi grupie u jednu (sloenu) naredbu. Blok se obi cno nalazi unutar drugih sloenih

1.2. Naredbe

17

naredbi kada je potrebno da se vie naredbi sintaksno objedine u jednu naredbu. U optem slu caju, medutim, blok se moe nalaziti na bilo kom mestu u programu gde je dozvoljeno pisati neku naredbu. Efekat izvravanja bloka naredbi je sekvencijalno izvravanje niza naredbi unutar bloka. Naime, od otvorene viti caste zagrade bloka, sve naredbe u nizu koji sledi izvravaju se redom jedna za drugom, sve do zatvorene viti caste zagrade bloka. U slede cem primeru bloka naredbi, medusobno se zamenjuju vrednosti celobrojnih promenljivih x i y, pod pretpostavkom da su ove promenljive u programu deklarisane i inicijalizovane ispred bloka:
{ int z = x = y = } z; x; y; z; // // // // pomo cna promenljiva vrednost z je stara vrednost x nova vrednost x je stara vrednost y nova vrednost y je stara vrednost x

Ovaj blok c ini niz od 4 naredbe koje se redom izvravaju. Primetimo da je pomo cna promenljiva z deklarisana unutar samog bloka. To je potpuno dozvoljeno i, u stvari, dobar stil programiranja nalae da treba deklarisati promenjivu unutar bloka ako se ona nigde ne koristi van tog bloka. Promenljiva koja je deklarisana unutar nekog bloka, potpuno je nedostupna van tog bloka, jer se takva promenljiva unitava nakon izvravanja njenog bloka, odnosno memorija koju zauzima promenljiva se oslobada za druge svrhe. Za promenljivu deklarisana unutar bloka se kae da je lokalna za svoj blok ili da je taj blok njena oblast vaenja. (Ta cnije, oblast vaenja lokalne promenljive je deo bloka od mesta deklaracije promenljive do kraja bloka.)

Naredbe if i if-else
Naredba if je najprostija upravlja cka naredba koja omogu cava uslovno izvravanje niza od jedne ili vie naredbi. Svaka naredba if se sastoji od jednog logi ckog izraza i jedne (blok) naredbe koji se u otem obliku piu na slede ci na cin:
if (logi cki-izraz) naredba

Izvravanje naredbe if se izvodi tako to se najpre izra cunava vrednost logi ckog izraza u zagradi. Zatim, zavisno od toga da li je dobijena vrednost logi ckog izraza ta cno ili neta cno, naredba koja se nalazi u produetku logi ckog izraza izvrava se ili preska ce ako je dobijena vrednost ta cno (true), ta

18

1. O SNOVE J AVE

naredba se izvrava; u suprotnom slu caju, ako je dobijena vrednost neta cno (false), ta naredba se ne izvrava. Time se u oba slu caja izvravanje naredbe if zavrava, a izvravanje programa se dalje nastavlja od naredbe koja sledi iza naredbe if. Konkretan primer jedne naredbe if je:
if (r != 0) obim = 2 * r * Math.PI;

U ovom primeru se if naredbom proverava da li je vrednost promenljive r (polupre cnik kruga) razli cita od nule. Ako je to slu caj, izra cunava se obim kruga; u suprotnom slu caju, preska ce se izra cunavanje obima kruga. Kako niz naredbi unutar viti castih zagrada predstavlja jednu (blok) naredbu, naredba unutar if naredbe moe biti i blok naredbi:5
if (x > y) { int z; z = x; x = y; y = z; }

U ovom primeru se izvrava medusobna zamena vrednosti promenljivih x i y samo ukoliko je vrednost promenljive x po cetno ve ca od vrednosti promenljive y. U suprotnom slu caju se ceo blok od c etiri naredbe preska ce. Primetimo da je posle izvravanja ove if naredbe sigurno to da je vrednost promenljive x manja ili jednaka vrednosti promenljive y. U slu caju kada je vrednost logi ckog izraza if naredbe jednaka neta cno, programska logika c esto nalae da treba uraditi neto drugo, a ne samo presko citi naredbu unutar if naredbe. Za takve slu cajeve slui naredba if-else c iji je opti oblik:
if (logi cki-izraz) naredba1 else naredba2

Izvravanje if-else naredbe je sli cno izvravanju if naredbe, osim to u slu caju kada je vrednost logi ckog izraza u zagradi jednaka neta cno, izvrava se naredba2 i preska ce naredba1 . U drugom slu caju, kada je vrednost logi ckog izraza jednaka ta cno, izvrava se naredba1 i preska ce naredba2 . Time se u oba slu caja izvravanje if-else naredbe zavrava, a izvravanje programa se dalje nastavlja od naredbe koja sledi iza if-else naredbe.
5 Pridravamo se preporuke dobrog stila Java programairanja da se znak {, koji ozna cava

po cetak bloka naredbi, pie na kraju reda iza kojeg sledi, a odgovaraju ci znak } da se pie propisno poravnat u novom redu.

1.2. Naredbe

19

Obratite panju na to da se kod if-else naredbe izvrava ta cno jedna od dve naredbe unutar if-else naredbe. Te naredbe predstavljaju alternativne tokove izvravanja programa koji se biraju zavisno od vrednosti logi ckog izraza. Naravno, naredba1 i naredba2 u optem obliku if-else naredbe mogu biti blok naredbi. Na primer:
if (r <= 0) System.out.println("Greka: polupre cnik nije pozitivan!"); else { obim = 2 * r * Math.PI; System.out.println("Obim kruga je: " + obim); }

U ovom primeru if-else naredbe se u if delu nalazi samo jedna naredba i zato ta naredba ne mora (ali moe) da bude unutar para viti castih zagrada.6 S druge strane, else deo se sastoji od dve naredbe i zato se mora nalaziti unutar para viti castih zagrada u formi bloka. Ne samo da sastavni delovi obi cne if naredbe ili if-else naredbe (ili bilo koje sloene naredbe) mogu biti blok naredbi, nego ti delovi mogu biti i bilo koja naredbe jezika Java. Speci cno, ti delovi mogu biti druge if ili if-else naredbe. Jedan primer ove mogu cnosti koja se naziva ugnjedavanje jeste:
if (logi cki-izraz1 ) naredba1 else if (logi cki-izraz2 ) naredba2 else naredba3

U ovom primeru se u else delu prve if-else naredbe nalazi druga if-else naredba. Ali zbog slobodnog formata, ovo se skoro uvek pie u drugom obliku:
if (logi cki-izraz1 ) naredba1 else if (logi cki-izraz2 ) naredba2 else naredba3

Ovaj drugi oblik sa klauzulom else if je bolji jer je razumljiviji, naroc ito u slu caju potrebe za ve cim brojem nivoa ugnjedavanja radi formiranja naredbe viestrukog grananja:
6 Neki programeri uvek koriste blokove za sastavne delove sloenih naredbi, c ak i kada se ti delovi sastoje od samo jedne naredbe.

20

1. O SNOVE J AVE

if (logi cki-izraz1 ) naredba1 else if (logi cki-izraz2 ) naredba2 else if (logi cki-izraz3 ) naredba3 . . . else if (logi cki-izrazn ) naredban else naredban +1

Izvravanje ove naredbe viestrukog grananja se izvodi tako to se najpre logi cki izrazi u zagradama izra cunavaju redom odozgo na dole dok se ne dobije prvi koji daje vrednost ta cno. Zatim se izvrava njegova pridruena naredba i preska ce se sve ostalo. Ukoliko vrednost nijednog logi ckog izraza nije ta cno, izvrava se naredba u poslednjem else delu. U stvari, ovaj else deo na kraju nije obavezan, pa ako nije naveden i nijedan logi cki izraz nema vrednost ta cno, nijedna od n naredbi se ni ne izvrava. Naglasimo jo da svaka od naredbi unutar ove naredbe viestrukog grananja moe biti blok koji se sastoji od niza naredbi izmedu castih zagrada. viti To naravno ne menja nita konceptualno to se ti ce izvravanja naredbe viestrukog grananja, osim to se izvrava niz naredbi pridruen prvom logi ckom izrazu koji ima vrednost ta cno. Jedan primer upotrebe naredbe viestrukog grananja je slede ci programski fragment u kojem se odreduje ocena studenta na ispitu na osnovu broja osvojenih poena i standardne skale:
// Podrazumeva se 0 <= brojPoena <= 100 if (brojPoena >= 91) ocena = 10; else if (brojPoena >= 81) ocena = 9; else if (brojPoena >= 71) ocena = 8; else if (brojPoena >= 61) ocena = 7; else if (brojPoena >= 51) ocena = 6; else ocena = 5;

Na kraju, obratimo panju na jedan problem u vezi sa ugnjedavanjem koji se popularno naziva vise ci else deo. Razmotrimo slede ci programski fragment:

1.2. Naredbe

21

if (x >= 0) if (y >= 0) System.out.println("Prvi slu caj"); else System.out.println("Drugi slu caj");

Ovako kako je napisan ovaj fragment, izgleda kao da se radi o jednoj naredbi if-else c iji se if deo sastoji od druge naredbe if. Medutim, kako uvla cenje redova nema zna caja za Java prevodilac i kako u Javi vai pravilo da se else deo uvek vezuje za najblii prethodni if deo koji ve c nema svoj else deo, pravi efekat prethodnog fragmenta je kao da je napisano:
if (x >= 0) if (y >= 0) System.out.println("Prvi slu caj"); else System.out.println("Drugi slu caj");

Efekat ovog fragmenta i sugerisana prvobitna interpretacija nisu ekvivalentni. Ukoliko promenljiva x ima vrednost manju od 0, efekat prvobitne interpretacije je prikazivanje teksta Drugi slu caj na ekranu. Ali pravi efekat za x manje od 0 jeste zapravo da se preska ce ugnjedena if-else naredba, odnosno nita se ne prikazuje na ekranu. Ukoliko se zaista eli efekat koji sugerie prvobitna interpretacija, on se if naredba se moe pisati moe obezbediti na dva na cina. Prvo, ugnjedena unutar bloka:
if (x >= 0) { if (y >= 0) System.out.println("Prvi slu caj"); } else System.out.println("Drugi slu caj");

Drugo reenje je da se ugnjedena if naredba pie u obliku if-else na redba c iji se else deo sastoji od takozvane prazne naredbe ozna cene samo ta cka-zapetom:
if (x >= 0) if (y >= 0) System.out.println("Prvi slu caj"); else ; // prazna naredba else System.out.println("Drugi slu caj");

22

1. O SNOVE J AVE

Naredba switch
Tre ca naredba grananja, naredba switch, na neki na cin je specijalni sluc aj naredbe viestrukog grananja iz prethodnog odeljka kada izbor jednog od vie alternativnih blokova naredbi za izvravanje zavisi od vrednosti datog celobrojnog ili znakovnog izraza.7 Naime, u tom slu caju je neekasno da se izra cunavanje tog izraza ponavlja u viestruko ugnjedenim if naredbama. Naj ce ci oblik naredbe switch je:
switch (izraz) { case konstanta1 : niz-naredbi1 break; case konstanta2 : niz-naredbi2 break; . . . case konstantan : niz-naredbin break; default: niz-naredbin +1 } // izvri odavde ako izraz == konstanta1 // kraj izvravanja tog slu caja // izvri odavde ako izraz == konstanta2 // kraj izvravanja tog slu caja

// izvri odavde ako izraz == konstantan // kraj izvravanja tog slu caja // izvri odavde u krajnjem slu caju ... // kraj izvravanja tog slu caja

Ovom switch naredbom se najpre izra cunava izraz u zagradi na po cetku naredbe. Zatim se zavisno od dobijene vrednosti tog izraza izvrava niz naredbi koji je pridruen jednom od slu cajeva ozna cenih klauzulama case unutar switch naredbe. Pri tome se redom odozgo na dole trai prva konstanta uz klauzulu case koja je jednaka vrednosti izra cunatog izraza i izvrava se odgovaraju ci niz naredbi pridruen pronadenom slu caju. Poslednji slu caj u switch naredbi moe opciono biti ozna cen klauzulom default, a taj slu caj se izvrava ukoliko izra cunata vrednost izraza nije jednaka nijednoj konstanti uz klauzule case. Najzad, ukoliko klauzula default nije navedena i izra cunata vrednost izraza nije jednaka nijednoj konstanti uz klauzule case, nita se dodatno ne izvrava nego se izvravanje switch naredbe time odmah zavrava. Na kraju svakog slu caja switch naredbe se obi cno, ali ne uvek, navodi break naredba. O break naredbi se govori kasnije u ovom poglavlju. Rezultat break naredbe je prekid izvravanja odgovaraju ceg slu caja, a time i cele switch naredbe. Ako se na kraju nekog slu caja koji se izvrava ne nalazi break naredba, prelazi se na izvravanje narednog slu caja unutar switch naredbe.
7 Izraz u naredbi switch moe biti i nabrojivog tipa. O nabrojivim tipovima podataka se govori u poglavlju 5.

1.2. Naredbe

23

Ovaj prelazak na naredni slu caj se nastavlja sve dok se ne naide na prvu break naredbu ili dok se ne dode do kraja switch naredbe. U nekom od slu cajeva unutar switch naredbe, niz naredbi i break naredba mogu potpuno biti izostavljeni iza zapisa case konstantai :. Ako iza tog praznog slu caja dolazi drugi pravi slu caj, onda niz naredbi pridruen ovom drugom slu caju odgovara zapravo dvema konstantama. To prosto zna ci da c e se ovaj niz naredbi izvriti kada je vrednost izraza jednaka jednoj od te dve konstante. U slede cem primeru su pokazane neke od prethodno pomenutih mogu cnosti switch naredbe. Obratite panju i na to da se konstante uz klauzule case mogu pisati u proizvoljnom redosledu, pod uslovom da su sve razli cite.
// n je celobrojna promenljiva switch (2 * n) { case 1: System.out.println("Ovo se nikad ne ce izvriti,"); System.out.println("jer je 2n paran broj!"); break; case 2: case 4: case 8: System.out.println("n ima vrednost 1, 2 ili 4,"); System.out.println("jer je 2n jednako 2, 4 ili 8."); break; case 6: System.out.println("n ima vrednost 3."); break; case 5: case 3: System.out.println("I ovo je nemogu ce!"); break; }

Naredba while
Naredba while je osnovna naredba za ponavljanje izvravanja neke naredbe (ili bloka naredbi) vie puta. Poto cikli cno izvravanje bloka naredbi obrazuje petlju u sledu izvravanja naredbi programa, while naredba se c esto naziva i while petlja. Blok naredbi unutar while petlje c ije se izvravanje ponavlja naziva se telo petlje. Broj ponavljanja tela petlje se kontrolie vredno cu jednog logi ckog izraza. Ovaj logi cki izraz se naziva uslov nastavka (ili uslov prekida) petlje, jer se izvravanje tela petlje ponavlja sve dok je vrednost tog logi ckog izraza jednaka ta cno, a ovo ponavljanje se prekida u trenutku kada vrednost tog logi ckog iz-

24

1. O SNOVE J AVE

raza postane neta cno. Ponovljeno izvravanje tela petlje se moe, u principu, izvoditi beskona cno, ali takva beskona cna petlja obi cno predstavlja greku u programu. Opti oblik while naredbe je:
while (logi cki-izraz) naredba

Izvravanje while naredbe se izvodi tako to se najpre izra cunava vrednost logi ckog izraza u zagradi. U jednom slu caju, ako je dobijena vrednost jednaka neta cno, odmah se prekida izvravanje while naredbe. To zna ci da se preska ce ostatak while naredbe i izvravanje programa se normalno nastavlja od naredbe koja sledi iza while naredbe. U drugom slu caju, ako izra cunavanje logi ckog izraza daje ta cno, najpre se izvrava naredba unutar while naredbe, a zatim se ponovo izra cunava logi cki izraz u zagradi i ponavlja isti ciklus. To jest, ako logi cki izraz daje neta cno, prekida se izvravanje while naredbe; ako logi cki izraz daje ta cno, ponovo se izvrava naredba unutar while naredbe, opet se izra cunava logi cki izraz u zagradi i zavisno od njegove vrednosti dalje se opet ili ponavlja ovaj postupak ili se prekida izvravanje while naredbe. cno i Telo petlje koje c ini naredba unutar while naredbe moe biti, a obi jeste, blok naredbi, pa while naredba ima c esto ovaj oblik:
while (logi cki-izraz) { niz-naredbi }

Efekat while naredbe je dakle ponavljanje jedne naredbe ili niza naredbi u bloku sve dok je vrednost logi ckog izraza jednaka ta cno. U trenutku kada ta vrednost postane neta cno, while naredba se zavrava i nastavlja se normalno izvravanje programa od naredbe koja sledi iza while naredbe. U narednom jednostavnom primeru while naredbe se na ekranu prikazuje niz brojeva 0, 1, 2, . . ., 9 u jednom redu:
int broj = 0; while (broj < 10) { System.out.print(broj + " "); broj = broj + 1; } System.out.println(); // pomeranje kursora u novi red

U ovom primeru se na po cetku celobrojna promenljiva broj inicijalizuje vredno cu 0. Zatim se izvrava while naredba, kod koje se najpre odreduje vrednost logi ckog izraza broj < 10. Vrednost ovog izraza je ta cno, jer je trenutna vrednost promenljive broj jednaka 0 i, naravno, broj 0 je manji od broja

1.2. Naredbe

25

10. Zato se izvravaju dve naredbe bloka u telu petlje, odnosno na ekranu se prikazuje vrednost 0 promenljive broj i ta vrednost se uve cava za 1. Nova vrednost promenljive broj je dakle 1. Slede ci korak kod izvravanja while naredba je ponovno izra cunavanje logi ckog izraza broj < 10. Vrednost ovog izraza je opet ta cno, jer je trenutna vrednost promenljive broj jednaka 1 i, naravno, broj 1 je manji od broja 10. Zato se opet izvravaju dve naredbe u telu petlje, odnosno na ekranu se prikazuje trenutna vrednost 1 promenljive broj i ta vrednost se uve cava za 1. Nova vrednost promenljive broj je dakle 2. Poto izvravanje while naredbe jo nije zavreno, ponovo se izra cunava logi cki izraz broj < 10. Opet se dobija da je njegova vrednost jednaka ta cno, jer je trenutna vrednost promenljive broj jednaka 2 i, naravno, broj 2 je manji od broja 10. Zato se opet izvrava telo petlje, izra cunava se uslov prekida petlje i tako dalje. U svakoj iteraciji petlje se prikazuje aktuelna vrednost promenljive broj i ta vrednost se uve cava za 1, pa je jasno je da c e se to ponavljati sve dok promenljiva broj ne dobije vrednost 10. U tom trenutku c e logi cki izraz broj < 10 imati vrednost neta cno, jer 10 nije manje od 10, a to predstavlja uslov prekida izvravanja while naredbe. Nakon toga se izvravanje programa nastavlja od naredbe koja se nalazi iza while naredbe, to u ovom primeru dovodi do pomeranja kursora na ekranu u novi red. Potrebno je na kraju razjasniti jo neke detalje u vezi sa while naredbom. Prvo, ta se dogada ckog izraza while naredbe jednaka ako je vrednost logi neta cno pri prvom njegovom izra cunavanju, kada se telo petlje jo nijedanput nije izvrilo? U tom slu caju, izvravanje while naredbe se odmah zavrava i zato se telo petlje nikad ni ne izvrava. To zna ci da broj iteracija while naredbe moe biti proizvoljan, uklju cuju ci nulu. Drugo, ta se dogada ckog izraza while naredbe postane ako vrednost logi neta cno negde u sredini izvravanja tela petlje? Da li se while naredba odmah tada zavrava? Odgovor je negativan, jer se telo petlje izvrava do kraja. Tek kada se potom ponovo izra cuna logi cki izraz i dobije njegova neta cna vrednost, dolazi do zavretka izvravanja while naredbe. Tre ce, u telu while naredbe se mogu nalaziti proizvoljne naredbe, pa tako i druge petlje. Ako se jedna petlja nalazi unutar tela druge petlje, onda se govori o ugnjedenim petljama.

Naredba do-while
Kod while naredbe se uslov prekida petlje proverava na po cetku svake iteracije. U nekim slu cajevima, medutim, prirodnije je proveravati taj uslov na kraju svakog izvravanja tela petlje. U takvim slu cajevima se moe koristiti

26

1. O SNOVE J AVE

do-while naredba koja je vrlo sli cna while naredbi, osim to su re c while i

uslov prekida pomereni na kraj, a na po cetku se nalazi slubena re c do. Opti oblik do-while naredbe je:
do naredba while (logi cki-izraz);

ili, ako je naredba unutar do-while naredbe jedan blok naredbi, taj oblik je:
do { niz-naredbi } while (logi cki-izraz);

Na primer, do-while naredbom se prikazivanje brojeva 0, 1, 2, . . ., 9 u jednom redu na ekranu moe posti ci na slede ci na cin:
int broj = 0; do { System.out.print(broj + " "); broj = broj + 1; } while (broj < 10); System.out.println(); // pomeranje kursora u novi red

Obratite panju na ta cka-zapetu koja se nalazi na samom kraju opteg oblika do-while naredbe. Ta cka-zapeta je deo do-while naredbe i ne moe se izostaviti. (Generalno, na kraju svake naredbe u Javi se mora nalaziti ta ckazapeta ili zatvorena viti casta zagrada.) Kod izvravanja do-while naredbe se najpre izvrava telo petlje, odnosno izvrava se naredba ili niz naredbi u bloku izmedu ci do i while. slubenih re Zatim se izra cunava vrednost logi ckog izraza u zagradi. U jednom slu caju, ako je ta vrednost jednaka neta cno, prekida se izvravanje do-while naredbe i nastavlja se normalno izvravanje programa od naredbe koja sledi iza do-while naredbe. U drugom slu caju, ako izra cunavanje logi ckog izraza daje ta cno, izvravanje se vra ca na po cetak tela petlje i ponavlja se ciklus od po cetka. Primetimo da, poto se logi cki izraz izra cunava na kraju iteracije svakog ciklusa, telo petlje se kod do-while naredbe izvrava bar jedanput. To se razlikuje od while naredbe kod koje se telo petlje ne mora uopte izvriti. Napomenimo i to da se do-while naredba moe uvek simulirati while naredbom (a i obrnuto), jer je efekat do-while naredbe ekvivalentan slede cem programskom fragmentu:
naredba while ( logi cki-izraz ) naredba

1.2. Naredbe

27

Naredba for
Tre ca vrsta petlji u Javi se obi cno koristi kada je potrebno izvriti telo petlje za sve vrednosti odredene promenljive u nekom intervalu. Za prikazivanje brojeva 0, 1, 2, . . ., 9 u jednom redu na ekranu, na primer, koristili smo naredbe while i do-while. U tim naredbama se zapravo telo petlje sastoji od prikazivanja promenljive broj, a ovo telo petlje se izvrava za sve vrednosti promenljive broj u intervalu od 0 do 9. U ovom slu caju je mnogo prirodnije koristiti naredbu for, jer je odgovaraju ci ekvivalentni programski fragment mnogo kra ci i izraajniji:
for ( int broj = 0; broj < 10; broj = broj + 1 ) System.out.print(broj + " "); System.out.println(); // pomeranje kursora u novi red

Opti oblik for naredbe je:


for (kontrolni-deo) naredba

U drugom slu caju, ako se telo naredba for sastoji od bloka naredbi, njen opti oblik je:
for (kontrolni-deo) { niz-naredbi }

Kod for naredbe je kontrolnim delom na po cetku naredbe obuhva ceno na jednom mestu sve to je bitno za upravljanje petljom, a to doprinosi c itljivosti i boljem razumevanju logike petlje. Ovaj kontrolni deo je dalje podeljen u tri dela koji se medusobno razdvajaju ta cka-zapetom:
inicijalizacija; logi cki-izraz; zavrnica

Prema tome, opti oblik (proste) naredbe for je zapravo:


for (inicijalizacija; logi cki-izraz; zavrnica) naredba

Efekat izvravanja ove for naredbe se moe predstaviti slede cim programskim fragmentom u kome se koristi while naredba:
inicijalizacija while (logi cki-izraz) { naredba zavrnica }

Kod izvravanja for naredbe dakle, na samom po cetku se izvrava deo


inicijalizacija konrolnog dela, i to samo jedanput. Dalje se uslov nastavka

28

1. O SNOVE J AVE

for petlje predstavljen logi ckim izrazom izra cunava pre svakog izvravanja

tela for petlje, a izvravanje petlje se prekida kada izra cunata vrednost logi ckog izraza jeste neta cno. Deo zavrnica konrolnog dela se izvrava na kraju svakog izvravanja tela for petlje, neposredno posle izvravanja tela petlje i pre ponovne provere uslova nastavka petlje. Deo inicijalizacija moe biti bilo koji izraz, mada je to obi cno naredba dodele. Deo zavrnica moe takode cno biti bilo koji izraz, mada je to obi naredba inkrementiranja, dekrementiranja ili dodele. Interesantno je primetiti da svaki od tri dela u kontrolnom delu for naredbe moe biti prazan (ali se ta cka-zapete ne mogu izostaviti). Ako je logi cki izraz u kontrolnom delu izostavljen, podrazumeva se da umesto njega stoji true. Zato for naredba sa praznim logi ckim izrazom obrazuje beskona cnu petlju. U kontrolnom delu for naredbe, u delu inicijalizacija obi cno se nekoj promenljivi dodeljuje po cetna vrednost, a u delu zavrnica se vrednost te promenljive uve cava ili smanjuje za odreden korak. Pri tome se vrednost te promenljive jo proverava u logi ckom izrazu radi nastavka ili prekida petlje. Promenljiva koja se na ovaj na cin koristi u for naredbi se naziva kontrolna promenljiva ili kratko broja c petlje. Kako inicijalizacija tako i zavrnica u kontrolnom delu for naredbe mogu se sastojati od nekoliko izraza razdvojenih zapetama. Tako se u sledec em primeru istovremeno prikazuju brojevi od 0 do 9 i od 9 do 0 u dve kolone:
for ( int n = 0, int m = 9; n < 10; System.out.printf("%5d", n); // System.out.printf("%5d", m); // System.out.println; // } n++, m-- ) { kolona irine 5 mesta za n kolona irine 5 mesta za m prelazak kursora u novi red

Naredbe break i continue


Kod while petlje i do-while petlje se uslov prekida tih petlji proverava na po cetku odnosno na kraju svake iteracije. Sli cno, iteracije for petlje se zavravaju kada se ispuni uslov prekida u kontrolnom delu te petlje. Ponekad je ipak prirodnije prekinuti neku petlju u sredini izvravanja tela petlje. Za takve slu cajeve se moe koristiti break naredba koja ima jednostavan oblik:
break;

Kada se izvrava break naredba unutar neke petlje, odmah se prekida izvravanje te petlje i prelazi se na normalno izvravanje ostatka programa od naredbe koja sledi iza petlje. U petljama se sli cno moe koristiti i continue naredba c iji je oblik takode jednostavan:

1.2. Naredbe

29

continue;

Medutim, izvravanjem continue naredbe se samo preska ce ostatak aktu elne iteracije petlje. To zna ci da se, umesto prekidanja izvravanja cele petlje kao kod break naredbe, continue naredbom prekida izvravanje samo aktuelne iteracije petlje i nastavlja sa izvravanjem naredne iteracije petlje (ukljuc uju ci i izra cunavanje uslova prekida petlje radi provere da li treba uopte nastaviti izvravanje petlje). Razlika u dejstvu break i continue naredbi na primeru while petlje ilustrovana je na slici 1.3.
while ( ) { // break; // } // while ( ) { // continue; // } //

Slika 1.3: Efekat naredbi break i continue u petljama. S obzirom na to da se petlje mogu ugnjedavati jedna u drugu, ukoliko se break naredba nalazi u unutranjoj petlji, postavlja se pitanje koju petlju prekida break naredba unutranju ili spoljanju? Pravilo je da break naredba uvek prekida izvravanje samo prve obuhvataju ce petlje, ne i eventualnu spoljanju petlju koja obuhvata petlju u kojoj se neposredno nalazi break naredba. Sli cno pravilo vai i za continue naredbu: ako se continue naredba nalazi u ugnjedenoj petlji, ona prekida aktuelnu iteraciju samo te petlje i nema nikakvog uticaja na eventualne obuhvataju ce petlje. Ovo dejstvo naredbi break i continue u unutranjim petljama je prepreka da se prekine neki glavni postupak koji se sastoji od vie ugnjedenih petlji, a logi cko mesto prekida je duboko ugnjedena petlja. Takvi slu cajevi se c esto javljaju pri otkrivanju logi ckih greaka u programu kada vie nema smisla nastaviti dalju obradu, nego treba potpuno prekinuti zapo ceti postupak. Da bi se izvrilo potpuno prekidanje spoljanje petlje ili samo njene aktuelne iteracije, u Javi se mogu koristiti ozna cene petlje. Oznaka petlje se navodi ispred bilo koje vrste petlje u formi imena sa dvota ckom. Na primer, po cetak ozna cene while petlje sa oznakom spolj_petlja moe biti:
spolj_petlja: while (...) . . .

30

1. O SNOVE J AVE

Unutar tela ove petlje u nekoj unutranjoj petlji moe se zatim koristiti naredba
break spolj_petlja;

ili
continue spolj_petlja;

radi potpunog prekida ili prekida samo akteulne iteracije petlje ozna cene sa spolj_petlja. Efekat naredbi break i continue u ugnjedenim ozna cenim i neozna ce nim petljama ilustrovan je na slici 1.4.
oznaka: for ( ) { while ( ) { // break; // break oznaka; // } // oznaka: for ( ) { while ( ) { // continue; // continue oznaka; // } //

Slika 1.4: Efekat naredbi break i continue u ugnjedenim petljama. Pored toga to se naredbe break i continue mogu koristiti u svakoj vrsti petlje (while, do-while i for), naredba break se moe koristiti i za prekid izvravanja naredbe switch. U stvari, naredba break se moe koristili i unutar naredbe if (ili if-else), ali jedino u slu caju kada se sama naredba if nalazi unutar neke petlje ili naredbe switch. U tom slu caju, naredba break zapravo prekida izvravanje obuhvataju ce petlje ili naredbe switch, a ne same naredbe if.

1.3 Metodi
Uopteno govore ci, metodi u Javi su potprogrami koje obavljaju neki speci cni zadatak. Koncept potprograma postoji u svim programskim jezicima, ali se pojavljuje pod razli citim imenima: metod, procedura, funkcija, rutina i sli cno. Termin koji se u Javi koristi za potprogram je metod, u smislu postupak ili na cin za reavanje nekog zadatka.

1.3. Metodi

31

Metod je dakle samostalan, imenovan niz naredbi jezika Java koje se mogu koristiti u drugim delovima programa. Kori cenje metoda se tehni cki naziva pozivanje metoda za izvravanje. Svakim pozivom metoda se izvrava niz naredbi od kojih se metod sastoji. Pre po cetka izvravanja ovog niza naredbi, metodu se mogu preneti neke vrednosti koje se nazivaju argumenti poziva metoda. Isto tako, na kraju izvravanja ovog niza naredbi, metod moe vratiti neku vrednost koja se naziva rezultat izvravanja metoda. Odredena programska realizacija funkcije metoda naziva se denicija me toda. Denisanjem metoda se specicira niz naredbi i ostali elementi koji c ine metod. Medu ove elemente spadaju formalni parametri koji odreduju koji se ulazni podaci kao argumenti mogu preneti prilikom pozivanja metoda za izvravanje.

Denisanje metoda
Pre svega, vana osobina Jave je to to se denicija svakog metoda mora nalaziti unutar neke klase. Metod koji je c lan neke klase moe biti stati cki (klasni) ili nestati cki (objektni) c ime se odreduje da li metod pripada toj klasi kao celini ili pojedina cnim objektima te klase. Denicija metoda se sastoji od zaglavlja i tela metoda. Telo metoda ima oblik obi cnog bloka naredbi, odnosno telo metoda se sastoji od niza proizvoljnih naredbi izmedu castih zagrada. Zaglavlje metoda sadri sve in viti formacije koje su neophodne za pozivanje metoda. To zna ci da se u zaglavlju metoda, izmedu ostalog, nalazi: Ime metoda Tipovi i imena parametara metoda Tip vrednosti koju metod vra ca kao rezultat Razni modikatori kojima se dodatno odreduju karakteristike metoda Preciznije, opti oblik denicije metoda u Javi je:
modifikatori tip-rezultata ime-metoda (lista-parametara) { niz-naredbi }

Na primer, denicija glavnog metoda main() u programu obi cno ima slede ci oblik:
public static void main (String[] args) { // Zaglavlje metoda . . // Telo metoda . }

32

1. O SNOVE J AVE

U zaglavlju ove denicije metoda main(), slubene re ci public i static su primeri modikatora, void je tip rezultata, main je ime metoda i, na kraju, String[] args u zagradi c ini listu od jednog parametra. Deo modifikatori na po cetku zaglavlja nekog metoda nije obavezan, ali se moe sastojati od jedne ili vie slubenih re ci koje su medusobno razdvo jene razmacima. Modikatorima se odreduju izvesne karakteristike metoda. Tako, u primeru metoda main(), slubena re c public ukazuje da se metod moe slobodno koristiti u bilo kojoj drugoj klasi; slubena re c static ukazuje da je metod stati cki, a ne objektni, i tako dalje. Za metode je u Javi na raspolaganju ukupno oko desetak modikatora. O njima, kao i drugim elementima denicije metoda, govori se u kontekstu daljeg teksta knjige. Ako metod izra cunava jednu vrednost koja se vra ca kao rezultat poziva metoda, onda deo tip-rezultata u njegovom zaglavlju odreduje tip poda taka kojem pripada rezultuju ca vrednost metoda. S druge strane, ako metod ne vra ca nijednu vrednost, onda se deo tip-rezultata sastoji od slubene re ci void. (Time se eli ozna citi da tip nepostoje ce vra cene vrednosti predstavlja tip podataka koji ne sadri nijednu vrednost.) Formalna pravila za davanje imena metodu u delu ime-metoda jesu uobi cajena pravila za imena u Javi o kojima smo govorili na strani 9. Dodajmo ovde samo to da je neformalna konvencija za imena metoda u Javi ista kao i za imena promenljivih: prva re c imena metoda po cinje malim slovom, a ako se to ime sastoji od nekoliko re ci, onda se svaka re c od druge pie sa po cetnim velikim slovom. Naravno, opte nepisano pravilo je da imena metoda treba da budu smislena, odnosno da imena metoda dobro opisuju ta je funkcija pojedinih metoda. Na kraju zaglavlja metoda, deo lista-parametara u zagradi odreduje sve parametre metoda. Parametri su promenljive koje se inicijalizuju vrednostima argumenata prilikom pozivanja metoda za izvravanje. Parametri predstavljaju dakle ulazne podatke metoda koji se obraduju naredbama u telu metoda. Lista parametara metoda moe biti prazna (ali se zagrade moraju pisati), ili se ta lista moe sastojati od jednog ili vie parametara. Budu ci da parametar konceptualno predstavlja obi cnu promenljivu, za svaki parametar u listi navodi se njegov tip i njegovo ime u obliku:
tip-parametra ime-parametra

Ako lista sadri vie od jednog parametra, onda se parovi specikacija tih parametara medusobno razdvajaju zapetama. Primetimo da svaki par za tip i ime parametra mora ozna cavati samo jedan parametar. To zna ci da ako neki

1.3. Metodi

33

metod ima, recimo, dva parametra x i y tipa double, onda se u listi parametara mora pisati double x, double y, a ne skra ceno double x, y. U nastavku su prikazani jo neki primeri denicija metoda u Javi, bez naredbi u telu tih metoda kojima se realizuje njihova funkcija:
public static void odigrajPotez() { . . // Telo metoda . }

U ovom primeru su public i static modikatori; void je tip rezultata, tj. metod ne vra ca nijednu vrednost; odigrajPotez je ime metoda; lista parametara je prazna, tj. metod nema parametre.
int nacrtaj2DSliku(int n, int m, String naslov) { . . // Telo metoda . }

U ovom primeru nema modikatora u zaglavlju metoda; tip vra cene vrednosti metoda je int; ime metoda je nacrtaj2DSliku; lista parametara sadri tri parametra: n i m tipa int i naslov tipa String.
static boolean manjeOd(float x, float y) { . . // Telo metoda . }

U ovom primeru je static jedini modikator; tip vra cene vrednosti je boolean; ime metoda je manjeOd; lista parametara sadri dva parametra x i y tipa float. Drugi metod nacrtaj2DSliku() u prethodnim primerima je objektni (nestati cki) metod, jer njegovo zaglavlje ne sadri slubenu re c static. Generalno, metod se podrazumeva da je objektni ukoliko nije stati cki (to se odreduje modikatorom static). Modikator public koji je kori cen u prvom primeru ozna cava da je odigrajPotez() javni metod, odnosno da se moe pozivati i izvan klase u kojoj je denisan, c ak i u nekom drugom programu. Srodni modikator je private kojim se ozna cava da je metod privatan, odnosno da se moe pozivati samo unutar klase u kojoj je denisan. Modikatori public i private su takozvani specikatori pristupa kojima se odreduju ograni cenja pristupa metodu, odnosno na kojim mestima se metod moe koristiti. Tre ci, manje kori ceni specikator pristupa je protected kojim se

34

1. O SNOVE J AVE

njegovo pozivanje ograni cava na klasu u kojoj je denisan i sve njene izvedene klase. Ukoliko u zaglavlju metoda nije naveden nijedan specikator pristupa (kao u drugom i tre cem od prethodnih primera), onda se metod moe pozivati u svakoj klasi istog paketa kome pripada klasa u kojoj je metod denisan, ali ne i izvan tog paketa. Modikatori se u zaglavlju metoda moraju pisati ispred tipa rezultata metoda, ali njihov medusobni redosled nije bitan. Konvencija je ipak da se najpre pie eventualni specikator pristupa, zatim eventualno re c static i, na kraju, eventualno ostali modikatori.

Pozivanje metoda
Denicijom novog metoda se uspostavlja njegovo postojanje i odreduje kako on radi. Ali metod se uopte ne izvrava sve dok se ne pozove na nekom mestu u programu tek pozivom metoda se na tom mestu izvrava niz naredbi u telu metoda. (Ovo je ta cno c ak i za metod main() u nekoj klasi, iako se taj glavni metod ne poziva eksplicitno u programu, ve c ga implicitno poziva JVM na samom po cetku izvravanja programa.) Generalno, poziv nekog metoda u Javi moe imati tri oblika. Najprostiji oblik naredbe poziva metoda je:
ime-metoda(lista-argumenata)

Prema tome, na mestu u programu gde je potrebno izvravanje zadatka koji neki metod obavlja, navodi se samo njegovo ime i argumenti u zagradi koji odgovaraju parametrima metoda. Na primer, naredbom
nacrtaj2DSliku(100, 200, "Moja slika");

poziva se metod nacrtaj2DSliku() za izvravanje sa argumentima koji su navedeni u zagradi. Obratite panju na to da parametar u deniciji metoda mora biti neko ime, jer je parametar konceptualno jedna promenljiva. S druge strane, argument u pozivu metoda je konceptualno neka vrednost i zato argument moe biti bilo koji izraz c ije izra cunavanje daje vrednost tipa odgovaraju ceg parametra. Zbog toga, pre izvravanja naredbi u telu metoda, izra cunavaju se izrazi koji su navedeni kao argumenti u pozivu metoda i njihove vrednosti se prenose metodu tako to se dodeljuju odgovaraju cim parametrima. Pozivom metoda se dakle izvrava telo metoda, ali sa inicijalnim vrednostima parametara koje su prethodno dobijene izra cunavanjem vrednosti odgovaraju cih argumenata. Na primer, poziv metoda
nacrtaj2DSliku(i+j, 2*k, s)

1.3. Metodi

35

izvrava se tako to se izra cunava vrednost prvog argumenta i+j i dodeljuje prvom parametru, zatim se izra cunava vrednost drugog argumenta 2*j i dodeljuje drugom parametru, zatim se izra cunava vrednost tre ceg argumenta s ( ciji je rezultat aktuelna vrednost promenljive s) i dodeljuje se tre cem parametru, pa se s tim po cetnim vrednostima paramatera najzad izvrava niz naredbi u telu metoda nacrtaj2DSliku(). Ovaj na cin koji se u Javi primenjuje za prenoenje vrednosti argumenata u pozivu nekog metoda odgovaraju cim parametrima u deniciji metoda tehni cki se naziva prenoenje po vrednosti. Druga dva na cina pozivanja metoda u Javi zavise od toga da li je metod denisan da bude stati cki (klasni) ili nestati cki (objektni), odnosno da li je metod denisan s modikatorom static ili bez njega. Ako je metod stati cki i poziva se izvan klase u kojoj je denisan, onda se mora koristiti ta cka-notacija za njegov poziv:
ime-klase .ime-metoda(lista-argumenata)

Ovde je ime-klase ime one klase u kojoj je metod denisan.8 Stati cki metodi pripadaju celoj klasi, pa upotreba imena klase u ta cka-notaciji ukazuje na to u kojoj klasi treba na ci deniciju metoda koji se poziva. Na primer, naredbom
Math.sqrt(x+y)

poziva se stati cki metod sqrt() koji je denisan u klasi Math. Argument koji se prenosi metodu sqrt() je vrednost izraza x+y, a rezultat izvravanja tog metoda je kvadratni koren vrednosti njegovog argumenta. Na kraju, tre ci na cin pozivanja metoda koristi se za nestati cke (objektne) metode koji pripadaju pojedinim objektima, a ne celoj klasi. Zbog toga se oni pozivaju uz odgovaraju ci objekat koji predstavlja implicitni argument poziva metoda. Prema tome, ako je metod objektni i poziva se izvan klase u kojoj je denisan, onda je opti oblik ta cka-notacije njegovog poziva:
ime-objekta .ime-metoda(lista-argumenata)

Ovde je ime-objekta zapravo jedna promenljiva koja sadri referencu na onaj objekat za koji se poziva metod. Na primer, naredbom
re cenica.CharAt(i)

poziva se objektni metod CharAt() u klasi String, sa vredno cu promenljive i kao argumentom, za objekat klase String na koji ukazuje promenljiva re cenica. Rezultat ovog poziva je i -ti znak u stringu koji je referenciran promenljivom re cenica.
8 Stati cki metod se moe pozvati i preko nekog objekta klase u kojoj je metod denisan,

odnosno na isti na cin kao to se nestati cki (objektni) metod poziva. Medutim, takav na cin pozivanja stati ckog metoda se ne preporu cuje.

36

1. O SNOVE J AVE

Vra canje rezultata


Metod moe vra cati jednu vrednost koja se dobija kao rezultat poziva metoda. Ako je to slu caj, onda vra cena vrednost metoda mora biti onog tipa koji je naveden kao tip rezultata u deniciji metoda. Druga mogu cnost je da metod ne vra ca nijednu vrednost. U tom slu caju se tip rezultata u deniciji metoda sastoji od slubene re ci void. Primetimo da metod u Javi moe vra cati najvie jednu vrednost. Pozivi metoda koji vra caju jednu vrednost se navodi tamo gde je ta vrednost potrebna u programu. To je obi cno na desnoj strani znaka jednakosti kod naredbe dodele, ili kao argument poziva drugog metoda ili unutar nekog sloenijeg izraza. Pored toga, poziv metoda koji vra ca logi cku vrednost moe biti deo logi ckog izraza kod naredbi grananja i ponavljanja. (Dozvoljeno je navesti poziv metoda koji vra ca jednu vrednost i kao samostalnu naredbu, ali u tom slu caju se vra cena vrednost prosto zanemaruje.) U deniciji metoda koji vra ca jednu vrednost mora se navesti, pored tipa te vrednosti u zaglavlju metoda, bar jedna naredba u telu metoda c ijim izvravanjem se ta vrednost upravo vra ca. Ta naredba se naziva naredba povratka i ima opti oblik:
return izraz;

Ovde se tip vrednosti izraza iza slubene re ci return mora slagati sa navedenim tipom rezultata metoda koji se denie. U ovom obliku, naredba return ima dve funkcije: vra canje vrednosti izraza i prekid izvravanja pozvanog metoda. Preciznije, izvravanje naredbe return izraz u telu pozvanog metoda se odvija u dva koraka: 1. Izra cunava se navedeni izraz u produetku na uobi cajeni na cin. 2. Zavrava se izvravanje pozvanog metoda i izra cunata vrednost izraza i kontrola toka izvravanja prenose se na mesto u programu gde je metod pozvan. Da bismo bolje razumeli sve aspekte rada sa metodima, razmotrimo deniciju jednostavnog metoda za izra cunavanje obima pravouglog trougla ukoliko su date obe katete trougla:
double obim(double a, double b) { double c = Math.sqrt(a*a + b*b);// hipotenuza pravouglog trougla return a + b + c; // obim je zbir kateta i hipotenuze }

Iz zaglavlja metoda obim() se moe zaklju citi da taj metod ima dva parametra a i b tipa double koji predstavljaju vrednosti kateta pravouglog trougla. Tip rezultata metoda obim() je takode double, jer ako su katete realne vrednosti,

1.3. Metodi

37

onda i obim u optem slu caju ima realnu vrednost. Primetimo i da nije naveden nijedan modikator u zaglavlju ovog metoda, jer to ovde nije bitno i samo bi komplikovalo primer. (Podsetimo se da se u tom slu caju smatra da je metod objektni i da se moe pozivati u svim klasama iz istog paketa u kome se nalazi klasa koja sadri deniciju metoda.) U telu metoda obim() se najpre izra cunava hipotenuza pravouglog trougla po Pitagorinoj formuli, a zatim se vra ca rezultat metoda kao zbir kateta i hipotenuze pravouglog trougla. Kao to je ranije napomenuto, denisanjem metoda se nita ne izvrava, nego se tek pozivanjem metoda, u ta cki programa gde je potreban rezultat metoda, izvravaju naredbe u telu metoda uz prethodni prenos argumenata. Zbog toga, pretpostavimo da se na nekom mestu u jednom drugom metodu u programu izvravaju slede ce dve naredbe:
double k1 = 3, k2 = 4; // vrednosti kateta pravouglog trougla double O = obim(k1,k2); // poziv metoda obim()

Prvom naredbom deklaracije promenljivih k1 i k2 se, naravno, rezervie memorijski prostor za promenljive k1 i k2 i u odgovaraju cim memorijskim lokacijama se upisuju vrednosti, redom, 3 i 4. Drugom naredbom se rezervie memorijski prostor za promenljivu O, a zatim joj se dodeljuje izra cunata vrednost izraza koji se nalazi na desnoj strani znaka jednakosti. U okviru ovog izraza se nalazi poziv metoda obim() tako da je vrednost tog izraza, u stvari, vra cena vrednost poziva metoda obim(). Poziv tog metoda se dalje izvrava na na cin koji smo prethodno opisali: vrednosti argumenata u pozivu metoda se dodeljuju parametrima metoda u njegovoj deniciji, a sa tim inicijalnim vrednostima parametara se izvrava telo metoda u njegovoj deniciji. U konkretnom slu caju dakle, argumenti poziva metoda obim() su promenljive k1 i k2, pa se njihove trenutne vrednosti 3 i 4 redom dodeljuju parametrima ovog metoda a i b. Zatim se sa ovim inicijalnim vrednostima parametara a i b izvrava telo metoda obim(). Kontrola toka izvravanja se zato prenosi u telo metoda obim() i redom se izvravaju sve njegove naredbe dok se ne naide ci da se najpre izra cunava hipotenuza izra na naredbu return. To zna zom Math.sqrt(a*a + b*b) c ija se vrednost 3 3 + 4 4 = 9 + 16 = 25 = 5 dodeljuje promenljivoj c. Zatim se izvrava naredba return tako to se izra cunava izraz u produetku a + b + c i dobija njegova vrednost 3 + 4 + 5 = 12. Nakon toga se prekida izvravanje metoda obim() i izra cunata vrednost 12 i kontrola izvravanja se vra caju tamo gde je taj metod pozvan. To zna ci da se nastavlja izvravanje naredbe dodele O = obim(k1,k2) koje je bilo privremeno prekinuto radi izvravanja poziva metoda obim() na desnoj strani znaka jednakosti. Drugim re cima, vra cena vrednost 12 tog poziva se kona cno dodeljuje promenljivoj O.

38

1. O SNOVE J AVE

Iako u ovom primeru to nije slu caj, obratite panju na to da generalno naredba return ne mora biti poslednja naredba u telu metoda. Ona se moe pisati u bilo kojoj ta cki u telu metoda u kojoj se eli vratiti vrednost i zavriti izvravanje metoda. Ako se naredba return izvrava negde u sredini tela metoda, izvravanje metoda se odmah prekida. To zna ci da se sve eventualne naredbe iza return naredba preska cu i kontrola se odmah vra ca na mesto gde je metod pozvan. Naredba povratka se moe koristiti i kod metoda koji ne vra ca nijednu vrednost. Budu ci da tip rezultata takvog metoda mora biti prazan tip (tj. void) u zaglavlju metoda, naredba povratka u telu metoda u ovom slu caju ne sme sadrati nikakav izraz iza re ci return, odnosno ima jednostavan oblik:
return;

Efekat ove naredbe je samo zavretak izvravanja nekog metoda koji ne vra ca nijednu vrednost i vra canje kontrole na mesto u programu gde je taj metod pozvan. Upotreba naredbe povratka kod metoda koji ne vra ca nijednu vrednost nije obavezna i koristi se kada izvravanje takvog metoda treba prekinuti negde u sredini. Ako naredba povratka nije izvrena prilikom izvravanja tela takvog metoda, njegovo izvravanje se normalno prekida (i kontrola izvravanja se vra ca na mesto gde je metod pozvan) kada se dode do kraja izvravanja tela metoda. S druge strane, ukoliko metod vra ca jednu vrednost, u njegovom telu se mora nalaziti bar jedna naredba povratka u punom obliku return izraz, makar to bila poslednja naredba u telu metoda. (Ako ih ima vie od jedne, sve one moraju imati ovaj pun oblik, jer metod uvek mora vratiti jednu vrednost.)

Preoptere ceni metodi


Da bi se neki metod mogao pozvati radi izvravanja, mora se poznavati njegovo ime, kao i broj, redosled i tipovi njegovih parametara. Ove informacije se nazivaju potpis metoda. Na primer, metod
public void nekiMetod(int n, double x, boolean test) { . . // Telo metoda . }

ima potpis:
nekiMetod(int, double, boolean)

Obratite panju na to da potpis metoda ne obuhvata imena parametara metoda, nego samo njihove tipove. To je zato to za pozivanje nekog metoda

1.3. Metodi

39

nije potrebno znati imena njegovih parametara, ve c se odgovaraju ci argumenti u pozivu mogu navesti samo na osnovu poznavanja tipova njegovih parametara. Primetimo i da potpis metoda ne obuhvata tip rezultata metoda, kao ni eventualne modikatore u zaglavlju. Denicija svakog metoda u Javi se mora nalaziti unutar neke klase, ali jedna klasa moe sadrati denicije vie metoda sa istim imenom, pod uslovom da svaki takav metod ima razli cit potpis. Metodi iz jedne klase sa istim imenom i razli citim potpisom se nazivaju preoptere ceni (engl. overloaded) metodi. Na primer, u klasi u kojoj je denisan prethodni metod nekiMetod() moe se denisati drugi metod sa istim imenom, ali razli citim potpisom:
public void nekiMetod(String s) { . . // Telo metoda . }

Potpis ovog metoda je:


nekiMetod(String)

to je razli cito od potpisa prvog metoda nekiMetod(). Potpis dva metoda u klasi mora biti razli cit kako bi Java prevodilac ta cno mogao da odredi koji metod se poziva. A to se lako odreduje na osnovu broja, redosleda i tipova argumenata navedenih u pozivu metoda. Naredbom, na primer,
nekiMetod("super program");

o cigledno se poziva druga, a ne prva, verzija metoda nekiMetod(), jer je u pozivu naveden samo jedan argument tipa String.

Rekurzivni metodi
Metodi u Javi mogu u svojoj deniciji da pozivaju sami sebe. Takav na cin reavanja problema u programiranju se naziva rekurzivni na cin. Rekurzija predstavlja vrlo vanu i ekasnu tehniku za reavanje sloenih problema. Razmotrimo, na primer, izra cunavanje stepena x n , gde je x neki realan broj razli cit od nule i n ceo broj ve ci ili jednak nuli. Mada se za ovaj problem moe iskoristiti gotov metod Math.pow(x,n), poto stepen x n predstavlja mnoenje broja x sa samim sobom n puta, nije teko napisati i sopstveni metod za izra cunavanje stepena x n . Naime, n -ti stepen broja x moemo dobiti uzastopnim mnoenjem broja x sa parcijalno izra cunatim stepenima x i za i = 0, 1, . . . , n 1. Naredni metod stepen() koristi for petlju radi realizacije ovog iterativnog postupka:

40

1. O SNOVE J AVE

double stepen(double x, int n) { double y = 1; // rezultat for (int i = 0; i < n; i++) y = y * x; return y; }

Pored ovog klasi cnog (iterativnog) na cina, za izra cunavanje stepena moe se primeniti druga ciji (rekurzivni) pristup ukoliko se uo ci da je x 0 = 1 i x n = x x n 1 za stepen n ve ci od nule. Drugim re cima, ako je stepen n = 0, rezultat je uvek 1, dok se za stepen n > 0 rezultat dobija ako se x pomnoi sa (n 1)-im stepenom broja x . Drugi metod stepen2() je denisan tako da koristi ovaj na cin za izra cunavanje n -tog stepena broja:
double stepen2(double x, int n) { if (n == 0) return 1; else return x * stepen2(x,n-1); }

Obratite ovde posebnu panju na to da metod stepen2(), radi izra cunavanja n -tog stepena broja x , u naredbi return poziva sm sebe radi izra cunavanja (n 1)-og stepena broja x . Rekurzivni metodi su oni koji pozivaju sami sebe, bilo direktno ili indirektno. Metod direktno poziva sm sebe (kao u slu caju stepen2()) ako se u njegovoj deniciji nalazi poziv samog metoda koji se denie. Metod indirektno poziva sm sebe ako se u njegovoj deniciji nalazi poziv drugog metoda koji sa svoje strane poziva polazni metod koji se denie (bilo direktno ili indirektno). Rekurzivni metodi u optem slu caju reavaju neki zadatak svodenjem po laznog problema na sli can prostiji problem (ili vie njih). Na primer, metod stepen2() reava problem izra cunavanja n -tog stepena nekog broja svode njem na problem izra cunavanja (n 1)-og stepena istog broja. Prostiji zadatak (ili vie njih) onda se reava rekurzivnim pozivom metoda koji se denie. Vano je primetiti da reavanje prostijeg zadatka rekurzivnim pozivom sa svoje strane dovodi do svodenja tog prostijeg zadatka na jo prostiji za datak, a ovaj se onda opet reava rekurzivnim pozivim. Na primer, kod metoda stepen2() se problem izra cunavanja (n 1)-og stepena broja svodi na problem izra cunavanja (n 2)-og stepena tog broja. Naravno, ovaj proces upro cavanja polaznog zadatka se dalje nastavlja i zato se mora obezbediti da se zavri u odredenom trenutku. U suprotnom, dobija se beskona can lanac poziva istog metoda, to je programska graka sli cna beskona cnoj petlji.

1.3. Metodi

41

Zbog toga se u telu svakog rekurzivnog metoda mora razlikovati bazni slu caj za najprostiji zadatak c ije je reenje unapred poznato i koje se ne dobija rekurzivnim pozivom. To je kod metoda stepen2() slu caj kada je stepen n = 0, jer se onda rezultat 1 neposredno dobija bez daljeg rekurzivnog reavanja. Rekurzivni metod generie dakle lanac poziva za reavanje niza prostijih zadataka sve dok se ne dode caju. Tada do najprostijeg zadatka u baznom slu se lanac rekurzivnih poziva prekida i zapo ceti pozivi se zavravaju jedan za drugim u obrnutom redosledu njihovog pozivanja, odnosno sloenosti zadataka koje reavaju (od najprostijeg zadatka ka sloenijem zadacima). Na taj na cin, na kraju se dobija reenje najsloenijeg, polaznog zadatka. * * * Radi boljeg razumevanja osnovnih elemenata rekurzivne tehnike programiranja, razmotrimo poznat problem izra cunavanja n -tog broja Fibona cijevog niza. Fibona cijev niz brojeva po cinje sa prva dva broja koja su oba jednaka 1, a svaki slede ci broj u nizu se zatim dobija kao zbir prethodna dva broja niza. Tako, tre ci broj niza je zbir drugog i prvog, odnosno 1 + 1 = 2; c etvrti broj niza je zbir tre ceg i drugog, odnosno 2 + 1 = 3; peti broj niza je zbir c etvrtog i tre ceg, odnosno 3 + 2 = 5 i tako dalje. Prema tome, po cetni deo beskona cnog Fibona cijevog niza brojeva je: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, . . . Ako brojeve Fibona cijevog niza ozna cimo sa f 1 , f 2 , f 3 , . . . , onda je o cevidno rekurzivna denicija za n -ti broj Fibona cijevog niza: f 1 = 1, f 2 = 1 f n = f n 1 + f n 2 , n>2

Drugim re cima, prva dva broja f 1 i f 2 su jednaki 1, a n -ti broj za n > 2 jednak je zbiru prethodna dva, odnosno (n 1)-og i (n 2)-og broja Fibona cijevog niza. Ukoliko treba napisati metod za izra cunavanje n -tog broja Fibona cijevog niza, prethodna rekurzivna denicija za izra cunavanje tog broja moe se neposredno iskoristiti za rekurzivno reenje:
// Pretpostavlja se da je n 1 int fib(int n) { if (n <= 2) // bazni slu caj return 1; else return fib(n-1) + fib(n-2); }

42

1. O SNOVE J AVE

U ovom metodu obratite panju na to da se zadatak izra cunavanja n tog broja Fibona cijevog niza svodi na dva sli cna prostija zadatka, odnosno izra cunavanje (n 1)-og i (n 2)-og broja Fibona cijevog niza. Zato se pomo cu dva rekurzivna poziva fib(n-1) i fib(n-2) izra cunavaju vrednosti (n 1)-og i (n 2)-og broja Fibona cijevog niza i njihov zbir se kao rezultat vra ca za n -ti broj Fibona cijevog niza.

1.4 Uvod u klase


Posle promenljivih, operatora, izraza, naredbi i metoda, slede ci vii nivo programske konstrukcije u Javi su klase. Klasa slui za opis objekata sa zajedni ckim svojstvima koji se koriste u programu radi realizovanja funkcije programa. O ovoj glavnoj ulozi klasa na kojoj se zasniva objektno orijentisano programiranje opirno se govori u narednom poglavlju. Pre toga, medutim, potrebno je upoznati se sa nekim osnovnim pojmovima o klasama kako bi se mogli razumeti jo neki detalji koji imaju veze sa prostijim programskim elementima o kojima smo govorili u prethodnom delu ovog poglavlja. Denicije metoda nalaze se unutar klasa. Ali jedna klasa moe pored denicija metoda obuhvatati i deklaracije promenljivih. Ove promenljive, da bi se bolje razlikovale od obi cnih promenljivih koje se deklariu u metodima, nazivaju se i polja. Klasa je dakle jedna imenovana programska jedinica u kojoj se nalaze polja za c uvanje podataka i metodi za manipulisanje tim podacima. Kako klasa opisuje objekte koji joj pripadaju, denicijom neke klase se zapravo odreduju polja i metodi koje poseduju svi objekti te klase. U najprostijem slu caju, denicija klase ima slede ci oblik:
class ime-klase { . . // Telo klase . }

Drugim re cima, iza slubene re ci class navodi se ime klase, a zatim se u vitic astima zagradama u telu klase navode denicije polja i metoda. Na primer, denicija klase kojom se opisuju objekti koji predstavljaju zgrade moe imati ovaj izgled:
class Zgrada { int brojSpratova; String boja; String adresa;

1.4. Uvod u klase

43

public void okre ci(...) { ... }; public void dozidaj(...) { ... }; }

Klasa Zgrada sadri dakle tri polja brojSpratova, boja i adresa, kao i dva metoda okre ci() i dozidaj(). Klasa deniu ablon kako izgledaju pojedini objekti koji pripadaju klasi. Na primer, izgled svakog objekta klase Zgrada je prikazan na slici 1.5.
brojSpratova boja adresa

okre ci() dozidaj()

Slika 1.5: Jedan objekat klase Zgrada. Polja i metodi u klasi se jednim imenom nazivaju c lanovi klase. Polja se u klasi deniu izvan svih metoda, pa posmatrano iz perspektive metoda u klasi, polja su globalne promenljive za sve metode klase. S druge strane, promenljive koje su deklarisane unutar nekog metoda su lokalne promenljive za taj metod.9 Isto kao metodi, polja mogu biti stati cka (klasna) ili nestati cka (objektna). Stati cko polje pripada celoj klasi, odnosno jedno jedino stati cko polje dele svi objekti klase. Stati cko polje se denie pisanjem slubene re ci static ispred imena polja. Ukoliko se ispred imena polja ne nalazi modikator static, takvo polje se podrazumeva da je objektno. Objektna polja pripadaju pojedina cnim objektima klase, odnosno svaki objekat klase ima svoju kopiju objektnog polja. Na primer, sva polja u klasi Zgrada su objektna polja. Ako se u programu konstruiu, recimo, tri objekta te klase, svaki konstruisani objekat ima svoje primerke objektnih polja i metoda koji se nalaze u klasi Zgrada. Ova c injenica ilustrovana je na slici 1.6. U optem slu caju, deklaracija polja ima isti oblik kao deklaracija obi cne promenljive, uz dve razlike:
9 Moe se govoriti i o lokalnim promenljivima za blok, ukoliko su promenljive deklarisane unutar bloka ograni cenog viti castim zagradama.

44

1. O SNOVE J AVE

Objekat 1
brojSpratova

Objekat 2
3
brojSpratova

Objekat 3
brojSpratova

10

boja bela adresa . . .

boja lila adresa . . .

boja siva adresa . . .

okre ci() dozidaj()

okre ci() dozidaj()

okre ci() dozidaj()

Slika 1.6: Tri objekta klase Zgrada.

1. Deklaracija polja se mora nalaziti izvan svih metoda, ali naravno i dalje unutar neke klase. 2. Ispred tipa i imena polja se mogu nalaziti modikatori koji blie odreduju vrstu polja. Tu spadaju static, public i private. Neki primeri deklaracija polja su:
static String imeKorisnika; public int brojIgra ca; private static double brzina, vreme;

Modikatorom static se polje odreduje da bude stati cko; ako se ovaj modikator ne navede, podrazumeva se da je polje objektno. Modikatori pristupa public i private odreduju gde se polje moe koristiti. Polje sa mo dikatorom private (privatno polje) moe se koristiti samo unutar klase u kojoj je deklarisano. Za polje sa modikatorom public (javno polje) nema nikakvih ograni cenja u pogledu njegovog pristupa, odnosno ono se moe koristiti u bilo kojoj klasi. Sli cno kao kod metoda, ako se javno polje koristi u drugoj klasi od one u kojoj je deklarisano, onda se mora koristiti ta cka-notacija. Pri tome, ako je polje stati cko, taj zapis ima oblik ime-klase.ime-polja, dok se za objektna polja mora pisati ime-objekta.ime-polja. Ovde je ime-klase ona klasa u kojoj je javno stati cko polje deklarisano, a ime-objekta je neki konstruisani objekat klase u kojoj je objektno polje deklarisano.

Duina trajanja promenljivih


Svaka promenljiva ima svoj ivotni vek u memoriji tokom izvravanja programa. Postojanje neke promenljive u programu po cinje od trenutka izvravanja naredbe deklaracije promenljive. Time se rezervie memorijski pro-

1.4. Uvod u klase

45

stor odgovaraju ce veli cine za smetanje vrednosti promenljive. Ovaj postupak se naziva alociranje promenljive. Postojanje promenljive u programu se zavrava kada se oslobodi memorijski prostor koji je rezervisan za promenljivu, mogu ce da bi se iskoristio za neke druge potrebe. Ovaj postupak se naziva dealociranje promenljive, a momenat njegovog deavanja tokom izvravanja programa zavisi od vrste promenljive. U telu nekog metoda se mogu koristiti tri vrste promenljivih: lokalne promenljive deklarisane unutar metoda, parametri metoda i globalne promenljive deklarisane izvan metoda, ali u istoj klasi kao i metod. (Ove globalne promenljive su naravno drugo ime za polja klase.) Lokalne promenljive se dealociraju c im se izvri poslednja naredba metoda u kojem su deklarisane i neposredno pre povratka na mesto gde je taj metod pozvan. Stoga lokalne promenljive postoje samo za vreme izvravanja svog metoda, pa se zato ni ne mogu koristiti negde izvan svog metoda, jer tamo vie ne postoje. Lokalne promenljive dakle nemaju nikakve veze sa okruenjem metoda, odnosno one su potpuno deo internog rada metoda. Parametri metoda slue za prihvatanje ulaznih vrednosti metoda od argumenata kada se metod pozove. Kako parametri metoda imaju konceptualno istu ulogu kao lokalne promenljive, za parametre vai sli cno pravilo kao i za lokalne promenljive: paramatri metoda se alociraju u momentu poziva metoda za izvravanje i dealociraju u momentu zavretka izvravanja metoda. Globalna promenljiva metoda koja je deklarisane unutar neke klase postoji, grubo re ceno, od momenta kada se njena klasa prvi put koristi pa sve do kraja izvravanja celog programa. Ta cni trenuci alociranja i dealociranja globalnih promenljivih nisu toliko bitni, nego je vano samo to to su globalne promenljive u nekoj klasi nezavisne od metoda te klase i uvek postoje dok se metodi te klase izvravaju. Zato globalne promenljive dele svi metodi u istoj klasi. To zna ci da promene vrednosti globalne promenljive u jednom metodu imaju proireni efekat na druge metode: ako se u jednom metodu promeni vrednost nekoj globalnoj promenljivoj, onda se u drugom metodu koristi ta nova vrednost globalne promenljive ukoliko ona u cestvuje u, recimo, nekom izrazu. Drugim re cima, globalne promenljive su zajedni cke za sve metode neke klase, za razliku od lokalnih promenljivih (i parametara) koje pripadaju samo jednom metodu. Treba ista ci jo jedan detalj u vezi sa inicijalizacijom promenljivih prilikom njihovog alociranja. Lokalne promenljive metoda se u Javi ne inicijalizuju nikakvom vredno cu prilikom alociranja tih promenljivih na samom po cetku izvravanja metoda. Stoga one moraju imati eksplicitno dodeljenu vrednost u metodu pre nego to se mogu koristiti. Parametri metoda se prilikom alociranja na po cetku izvravanja metoda, naravno, inicijalizuju vred-

46

1. O SNOVE J AVE

nostima argumenata. Na kraju, globalne promenljive (polja) se automatski inicijalizuju unapred denisanim vrednostima. Za numeri cke promenljive, ta vrednost je nula; za logi cke promenljive, ta vrednost je false; i za znakovne promenljive, ta vrednost je Unicode znak \u0000. (Za objektne promenljive, ta inicijalna vrednost je specijalna vrednost null.)

Oblast vaenja imena


Programiranje se u svojoj sutini svodi na davanje imena. Neko ime u programu moe da ozna cava razne konstrukcije: promenljive, metode, klase i tako dalje. Ove konstrukcije se preko svojih imena koriste na raznim mestima u tekstu programu, ali u prethodnom odeljku smo pokazali da se, recimo, promenljive dinami cki alociraju i dealociraju tokom izvravanja programa. Teorijski je zato mogu ce da se neke programske konstrukcije koriste u tekstu programa iako one tokom izvravanja programa zi cki vie ne postoje u memoriji. Da bi se izbegle ove vrste greaka, u Javi postoje pravila o tome gde se uvedena (prosta) imena mogu koristiti u programu. Oblast teksta programa u kome se moe upotrebiti neko ime, radi kori cenja programske konstrukcije koju ozna cava, naziva se oblast vaenja imena (engl. scope). Promenljivoj se ime daje u deklaraciji promenljive. Oblast vaenja imena globalne promenljive (polja) je cela klasa u kojoj je globalna promenljiva denisana. To zna ci da se globalna promenljiva moe koristiti u tekstu cele klase, c ak i eventualno ispred naredbe njene deklaracije.10 S druge strane, oblast vaenja imena lokalne promenljive je od mesta njene deklaracije do kraja bloka u kome se njena deklaracija nalazi. Zato se lokalna promenljiva moe koristiti samo u svom metodu, i to od naredbe svoje deklaracije do kraja bloka u kojem se nalazi ta naredba deklaracije. Ova pravila za oblast vaenja imena promenljivih su ipak malo sloenija, jer je dozvoljeno lokalnoj promenljivoj ili parametru dati isto ime kao nekoj globalnoj promenljivoj. U tom slu caju, unutar oblasti vaenja lokalne promenljive ili parametra, globalna promenljiva je zaklonjena. Da bismo ovo ilustrovali, razmotrimo klasu IgraSaKartama c ija denicija ima slede ci oblik:
class IgraSaKartama { static String pobednik; // globalna promenljiva (polje)

static void odigrajIgru() {

10 Zato je mogu ce pisati deklaracije polja na kraju denicije klase, to je po nekima bolji stil pisanja klasa.

1.4. Uvod u klase

47

String pobednik; // lokalna promenljiva . . // Ostale naredbe metoda ... . } ... }

U telu metoda odigrajIgru(), ime pobednik se odnosi na lokalnu promenljivu. U ostatku klase IgraSaKartama, ime pobednik se odnosi na globalnu promenljivu (polje), sem ako to ime nije opet zaklonjeno istim imenom lokalne promenljive ili parametra u nekom drugom metodu. Ako se ipak mora koristiti stati cko polje pobednik unutar metoda odigrajIgru(), onda se mora pisati njegovo puno ime IgraSaKartama.pobednik. Ove komplikacije, kada lokalna promenljiva ili parametar imaju isto ime kao globalna promenljiva, uzrok su mnogih greaka koje se najjednostavnije izbegavaju davanjem razli citih imena radi lakeg razlikovanja. To je upravo i preporuka dobrog stila programiranja koje se treba pridravati. Drugi specijalni slu caj oblasti vaenja imena promenljivih pojavljuje se kod deklarisanja kontrolne promenljive petlje unutar for petlje:
for (int i = 0; i < n; i++) { . . // Telo petlje . }

U ovom primeru bi se moglo re ci da je promenljiva i deklarisana lokalno u odnosu na metod koji sadri for petlju. Ipak, oblast vaenja ovako deklarisane kontrolne promenljive je samo kontrolni deo i telo for petlje, odnosno ne protee se do kraja tela metoda koji sadri for petlju. Zbog toga se u Javi vrednost broja ca for petlje ne moe koristiti van te petlje. Na primer, ovo nije dozvoljeno:
for (int i = 0; i < n; i++) { . . // Telo petlje . } if (i == n) // GREKA: ovde ne vai ime i System.out.println("Zavrene sve iteracije");

Oblast vaenja imena parametra nekog metoda je blok od kojeg se sastoji telo tog metoda. Nije dozvoljeno redenisati ime parametra ili lokalne promenljive u oblasti njihovog vaenja, c ak ni u ugnjedenom bloku. Na primer, ni ovo nije dozvoljeno:

48

1. O SNOVE J AVE

void neispravanMetod(int n) { int x; while (n > 0) { int x; // GREKA: ime x je ve c definisano ... } }

Prema tome, u Javi se globalne promenljive mogu redenisati (ali su onda zaklonjene), dok se lokalne promenljive i parametri ne mogu redenisati. S druge strane, izvan oblasti vaenja lokalne promenljive ili parametra se njihova imena mogu ponovo davati. Na primer:
void ispravanMetod(int n) { while (n > 0) { int x; ... } // Kraj oblasti vaenja imena x while (n > 0) { int x; // OK: ime x se moe opet davati ... } }

Pravilo za oblast vaenja imena metoda je sli cno onom za globalne promenljive: oblast vaenja nekog metoda je cela klasa u kojoj je metod denisan. To zna ci da je dozvoljeno pozivati metod na bilo kom mestu u klasi, uklju cuju ci ta cke u programskom tekstu klase koje se nalaze ispred ta cke de je mogu nicije metoda. Cak ce pozivati neki metod i unutar denicije samog metoda. (U tom slu caju se radi, naravno, o rekurzivnim metodima.) Ovo opte pravilo ima dodatni uslov s obzirom na to da metodi mogu biti stati cki ili objektni. Naime, objektni c lanovi klase (metodi i polja), koji pripadaju nekom objektu klase, ne moraju postojati u memoriji dok se stati cki metod izvrava. Na primer, stati cki metod neke klase se moe pozvati upotrebom njegovog punog imena u bilo kojoj ta cki programa, a do tada objekat kome pripadaju objektni c lanovi iste klase moda nije jo bio ni konstruisan tokom izvravanja programa. Zato se objektni metodi i polja klase ne mogu koristiti u stati ckim metodima iste klase.

Paketi
Programski jezik Java (ta cnije Java platforma) sadri veliki broj unapred napisanih klasa koje se mogu koristiti u programima. Da bi se olakalo nalaenje i upotreba tih gotovih klasa, one su grupisane u pakete, analogno organizaciji datoteka i direktorijuma u okviru sistema datoteka na disku.

1.4. Uvod u klase

49

Paket u Javi je dakle kolekcija srodnih klasa koje c ine funkcionalnu ce11 linu. Glavne klase jezika Java nalaze se u paketima c ija imena po cinju preksom java. Na primer, najvanije klase su deo paketa java.lang; razne pomo cne klase nalaze se u paketu java.util; klase za programski ulaz i izlaz su u paketu java.io; klase za mreno programiranje su u paketu java.net; i tako dalje. Poslednja verzija Jave sadri preko 200 paketa i 5000 klasa! Sve pakete dakle ne vredi nabrajati, a pogotovo nije izvodljivo opisati sve klase u njima. Obi cno se namena paketa moe prepoznati na osnovu njegovog imena, ali ta cno poznavanje svake klase je prakti cno nemogu ce. Zbog toga je neophodno da se Java programeri dobro snalaze u kori cenju zvani cne dokumentacije jezika Java. Dokument u kome je detaljno opisana Java raspoloiv je u elektronskoj formi besplatno na Internetu. Dokumentacija je napisana u formatu pogodnom za c itanje u nekom brauzeru, tako da se jednostavnim izborom hiperveza u tekstu moe relativno brzo prona ci ono to se trai. Pored toga to olakavaju nalaenje i upotrebu gotovih klasa, paketi imaju jo dve dobre strane: 1. Paketima se spre cavaju sukobi imena klasa, jer razli citi paketi mogu da sadre klase sa istim imenom. 2. Paketima se omogu cava dodatni vid kontrole nad upotrebom klasa. Kori cenje paketa. Svaka klasa ima zapravo dva imena: prosto ime koje se daje u deniciji klase i puno ime koje dodatno sadri ime paketa u kome se klasa nalazi. Na primer, za manipulisanje datumima u programu je na raspolaganju klasa Date koja se nalazi u paketu java.util, pa je njeno puno ime java.util.Date. Klasa se bez ograni cenja moe koristiti samo pod punim imenom, ali upotreba duga ckog imena klase dovodi do c estih slovnih greaka i smanjuje c itljivost programa. Zbog toga postoji mogu cnost uvoenja pojedinih klasa na po cetku programa. Iza toga se u programu moe koristiti prosto ime klase bez imena paketa u kome se ona nalazi. Ova mogu cnost se postie pisanjem deklaracije import na po cetku teksta klase. Na primer, ukoliko je u klasi NekaKlasa potrebno koristi objekat klase Date, umesto dueg pisanja:
class NekaKlasa { ... java.util.Date d = new java.util.Date(); ... }
11 Paketi sadre i interfejse o kojima se govori u poglavlju 5.

50

1. O SNOVE J AVE

moe se kra ce pisati:


import java.util.Date; class NekaKlasa { ... Date d = new Date(); ... }

Ako je potrebno koristiti vie klasa iz istog ili razli citih paketa, onda za svaku klasu treba navesti posebne deklaracije import jednu ispod druge. Bolja mogu cnost je upotreba doker-znaka *, c ime se uvoze sve klase iz odredenog paketa. Prethodni primer moe se zato ekvivalentno napisati na slede ci na cin:
import java.util.*; class NekaKlasa { ... Date d = new Date(); ... }

U stvari, to je na cin koji se naj ce ce koristi u programima, jer se time veta cki ne pove cava veli cina programa, odnosno sve klase iz nekog paketa se zi cki ne dodaju programu. Deklaracija import ukazuje samo na to u kojem paketu treba traiti kori cene klase, to zna ci da se zi cki dodaju samo one klase koje se zaista pojavljuju u tekstu denicije nove klase. Denisanje paketa. Svaka klasa u Javi mora pripadati nekom paketu. Kada se denie nova klasa, ukoliko se druga cije ne navede, klasa automatski pripada jednom bezimenom paketu. Taj takozvani anonimni paket sadri sve klase koje nisu eksplicitno dodate nekom imenovanom paketu. Za dodavanje klase u imenovani paket koristi se deklaracija package kojom se odreduje paket kome pripada klasa. Ova deklaracija se navodi na sa mom po cetku teksta klase ( cak pre deklaracije import). Na primer, za dodavanje klase NekaKlasa u paket nekipaket pie se:
package nekipaket; import java.util.*; class NekaKlasa { . . // Telo klase . }

Odredivanje imenovanog paketa kome pripada nova klasa zahteva po sebnu organizaciju datoteka sa tekstom klase. U prethodnom primeru, da-

1.4. Uvod u klase

51

toteka NekaKlasa.java koja sadri tekst klase NekaKlasa mora se nalaziti u posebnom direktorijumu c ije ime mora biti nekipaket. U optem slu caju, ime posebnog direktorijuma mora biti isto kao ime paketa kojem klasa pripada. Pored toga, prevodenje i izvravanje klase NekaKlasa se mora obaviti iz direktorijuma na prvom prethodnom nivou od direktorijuma nekipaket.

G LAVA 2

K L ASE

Objektno orijentisano programiranje (OOP) je noviji pristup za reavanje problema na ra cunaru kojim se reenje trai na prirodan na cin koji bi se primenio i u svakodnevnom ivotu. U starijem, proceduralnom programiranju je programer morao da identikuje ra cunarski postupak za reenje problema i da napie niz programskih naredbi kojim se realizuje taj postupak. Naglasak u OOP nije na ra cunarskim postupcima nego na objektima softverskim elementima sa podacima i metodima koji mogu da dejstvuju jedni na druge. Objektno orijentisano programiranje se zato svodi na dizajniranje razli citih klasa objekata kojima se na prirodan na cin modelira problem koji se reava. Pri tome softverski objekti u programu mogu predstavljati ne samo realne, nego i apstraktne entitete iz odgovaraju ceg problemskog domena. U prethodnom poglavlju skoro da uopte nismo imali dodira sa objektno orijentisanim programiranjem, jer se pomenute osnovne programske konstrukcije mogu na ci, uz malo izmenjenu terminologiju, i u klasi cnim, proceduralnim programskim jezicima. Objektno orijentisane tehnike, u principu, mogu se koristiti u svakom programskom jeziku. To je posledica c injenice to objekte iz standardnog ugla gledanja na stvari moemo smatrati obi cnom kolekcijom promenljivih i procedura koje manipuliu tim promenljivim. Medutim, za efektivnu realizaciju objektno orijentisanog na cina reavanja pro blema je vrlo vano imati jezik koji to omogu cava na neposredan i elegantan na cin. Java je moderan objektno orijentisan programski jezik koji podrava sve koncepte objektno orijentisanog programiranja. Programiranje u Javi bez kori cenja njenih objektno orijentisanih mogu cnosti nije svrsishodno, jer su za proceduralno reavanje problema laki i pogodniji drugi jezici. U ovom poglavlju se zato obra ca posebna panja na detalje koncepta klase i njenog denisanja u Javi, kao i na potpuno razumevanje objekata i njihovog konstruisanja u programu.

54

2. K L ASE

2.1 Klase i objekti


Metodi su programske celine za obavljanje pojedina cnih speci cnih zadataka u programu. Na slede cem viem nivou sloenosti programskih jedinica su klase. Naime, kako smo u uvodnim napomenama o klasama u prethodnom poglavlju istakli, jedna klasa moe obuhvatati kako promenljive (polja) za c uvanje podataka, tako i vie metoda za manipulisanje tim podacima radi obavljanja razli citih zadataka. Pored ove organizacione uloge klasa u programu, druga njihova uloga je da opiu odredene objekte c ijim se manipulisanjem u programu ostvaruje potrebna funkcionalnost. U ovom odeljku se detaljnije govori o ovim ulogama klase.

Clanovi klase
Klasa obuhvata polja (globalne promenljive) i metode koji se jednim imenom nazivaju c lanovi klase. Ovi c lanovi klase mogu biti stati cki (to se prepoznaje po modikatoru static u deniciji c lana) ili nestati cki (objektni). S druge strane, govorili smo da klasa opisuje objekte sa zajedni ckim osobinama i da svaki objekat koji pripada nekoj klasi ima istu strukturu koja se sastoji od polja i metoda denisanih unutar njegove klase. Ta cnije je re ci da samo nestati cki deo neke klase opisuje objekte koji pripadaju toj klasi. Ali najta cnije iz programerske perspektive je da klasa slui za konstruisanje objekata. Drugim re cima, neka klasa je ablon za konstruisanje objekata, pri c emu svaki konstruisan objekat te klase sadri sve nestati cke c lanove klase. Za objekat konstruisan na osnovu denicije neke klase se kae da pripada toj klasi ili da je instanca te klase. Nestati cki c lanovi klase (polja i metodi), koji ulaze u sastav konstruisanih objekata, nazivaju se i objektni ili instancni c lanovi. Glavna razlika izmedu klase i objekata je dakle to to se klasa denie u tekstu programa, dok se objekti te klase konstruiu posebnim operatorom tokom izvravanja programa. To zna ci da se klasa stvara prilikom prevodenja programa i zajedno sa svojim stati ckim c lanovima postoji od po cetka do kraja izvravanja programa. S druge strane, objekti se stvaraju dinami cki tokom izvravanja programa i zajedno sa nestati ckim c lanovima klase postoje od trenutka njihovog konstruisanja, mogu ce negde u sredini programa, do trenutka kada vie nisu potrebni. Da bismo bolje razumeli ove osnovne koncepte klasa i objekata, posmatrajmo jednostavnu klasu koja moe posluiti za c uvanje osnovnih informacija o nekim korisnicima:
class Korisnik { static String ime;

2.1. Klase i objekti

55

static int id; }

Klasa Korisnik sadri stati cka polja Korisnik.ime i Korisnik.id, pa u programu koji koristi ovu klasu postoji samo po jedan primerak promenljivh Korisnik.ime i Korisnik.id. Taj program moe dakle modelirati situaciju u kojoj postoji samo jedan korisnik u svakom trenutku, jer imamo memorijski prostor za c uvanje podataka o samo jednom korisniku. Klasa Korisnik i njena dva stati cka polja Korisnik.ime i Korisnik.id postoje sve vreme izvravanja programa. Posmatrajmo sada sli cnu klasu koja sadri skoro ista (nestati cka) polja:
class Klijent { String ime; int id; }

U klasi Klijent su denisana nestati cka (objektna) polja ime i id, pa u ovom slu caju ne postoje promenljive Klijent.ime i Klijent.id. Ova klasa dakle ne sadri nita konkretno, osim ablona za konstruisanje objekata te klase. Ali to je veliki potencijal, jer ova klasa moe posluiti za stvaranje velikog broja objekata. Svaki konstruisani objekat ove klase ima ce svoje primerke polja ime i id. Zato program koji koristi ovu klasu moe modelirati vie klijenata, jer se po potrebi mogu konstruisati novi objekti za predstavljanje novih klijenata. Ako je to, recimo, neki bankarski program, kada klijent otvori ra cun u banci moe se konstruisati nov objekat klase Klijent za c uvanje podataka o tom klijentu. Kada neki klijent banke zatvori ra cun, objekat koji ga predstavlja u programu moe se ukloniti. Stoga u tom programu kolekcija objekata klase Klijent na prirodan na cin modelira rad banke. Primetimo da ovi primeri ne zna ce da klasa moe imati samo stati cke ili samo nestati cke c lanove. U nekim primenama postoji potreba za obe vrste c lanova klase. Na primer:
class ClanPorodice { static String prezime; String ime; int uzrast; void cestitajRo dendan() { uzrast++; System.out.println("Sre can " + uzrast + ". ro dendan!") } }

Ovom klasom se u programu mogu predstaviti c lanovi neke porodice. Poto je prezime nepromenljivo za sve c lanove porodice, nema potrebe taj po-

56

2. K L ASE

datak vezivati za svakog c lana porodice i zato se moe c uvati u jedinstve nom stati ckom polju ClanPorodice.prezime. S druge strane, ime i uzrast su karakteristi cni za svakog c lana porodice, pa se ti podaci moraju c uvati u nestati ckim poljima ime i uzrast. Obratite panju na to da se u denicji klase odreduju tipovi svih polja, i stati ckih i nestati ckih. Medutim, dok se aktuelne vrednosti stati ckih polja na laze u samoj klasi, aktuelne vrednosti nestati ckih (objektnih) polja se nalaze unutar pojedinih objekata, a ne klase. Sli cna pravila vae i za metode koji su denisani u klasi. Stati cki metodi pripadaju klasi i postoje za sve vreme izvravanja programa. Zato se stati cki metodi mogu pozivati u programu nezavisno od konstruisanih objekata njihove klase, pa c ak i ako nije konstruisan nijedan objekat. Denicije nestati ckih (objektnih) metoda se nalaze unutar teksta klase, ali takvi metodi logi cki pripadaju konstruisanim objektima, a ne klasi. To zna ci da se objektni metodi mogu primenjivati samo za neki objekat koji je prethodno konstruisan. Na primer, u klasi ClanPorodice je denisan objektni cestitajRo dendan() kojim se uzrast c metod lana porodice uva cava za 1 i pricestitajRo dendan() koji pripadaju rakazuje c estitka za rodendan. Metodi zli citim objektima klase estiClanPorodice obavljaju isti zadatak u smislu da c taju odgovaraju ci rodendan razli citim c lanovima porodice. Ali njihov stvarni efekat je razli cit, jer uzrasti c lanova porodice mogu biti razli citi. Generalno govore ci, denicija objektnih metoda u klasi odreduje zadatak koji takvi me todi obavljaju nad konstruisanim objektima, ali njihov speci cni efekat koji proizvode moe varirati od objekta do objekta, zavisno od aktuelnih vrednosti objektnih polja razli citih objekata. Prema tome, stati cki i nestati cki (objektni) c lanovi klase su vrlo razli citi koncepti i slue za razli cite svrhe. Vano je praviti razliku izmedu teksta denicije klase i samog pojma klase. Denicija klase odreduje kako klasu, tako i objekte koji se konstruiu na osnovu te denicije. Stati ckim delom denicije se navode c lanovi koji su deo same klase, dok se nestati ckim delom navode c lanovi koji c e biti deo svakog konstruisanog objekta.

Denicija klase
Opti oblik denicije obi cne klase u Javi je vrlo jednostavan:
modifikatori class ime-klase { telo-klase }

Modikatori na po cetku denicije klase nisu obavezni, ali se mogu sastojati od jedne ili vie slubenih re ci kojima se odreduju izvesne karakteristike

2.2. Promenljive klasnog tipa

57

klase koja se denie. Na primer, modikator pristupa public ukazuje na to da se klasa moe koristiti izvan svog paketa. Ina ce, bez tog modikatora, klasa se moe koristiti samo unutar svog paketa. Na raspolaganju su jo tri modikatora koje c emo upoznati u daljem tekstu knjige. Ime klase se gradi na uobi cajeni na cin, uz dodatnu konvenciju u Javi da sve re ci imena klase poc inju velikim slovom. Najzad, telo klase sadri denicije stati ckih i nestati ckih c lanova (polja i metoda) klase. Na primer, u programu se za predstavljanje studenata i njihovih rezultata na testovima za jedan predmet moe denisati klasa na slede ci na cin:
public class Student { public String ime; // ime studenta public int id; // mati cni broj studenta public double test1, test2, test3; // ocene na tri testa public double prosek() { // izra cunati prosek ocena return (test1 + test2 + test3) / 3; } }

Nijedan od c lanova klase Student nema modikator static, to zna ci da ta klasa nema stati ckih c lanova i da je zato ima smisla koristiti samo za konstruisanje objekata. Svaki objekat koji je instanca klase Student sadri polja ime, test1, test2 i test3, kao i metod prosek(). Ova polja u razli citim objektima ima ce generalno razli cite vrednosti. Metod prosek() primenjen za razli cite objekte klase Student dava ce zato razli cite rezultate, jer c e se za svaki objekat koristiti vrednosti njegovih polja. (Ovaj efekat je zapravo ta cno ono to se misli kada se kae da objektni metod pripada pojedina cnim objektima, a ne klasi.)

2.2 Promenljive klasnog tipa


Jedan aspekt denicije neke klase je to to se time opisuje struktura svih objekata koji pripadaju klasi. Drugi, moda vaniji aspekt denicije neke klase je to to se time uvodi novi tip podataka u programu. Vrednosti tog klasnog tipa su objekti same klase. Iako ova c injenica zvu ci pomalo tehni cki, ona ima dalekosene posledice. To zna ci da se ime denisane klase moe koristiti za tip promenljive u naredbi deklaracije, kao i za tip formalnog parametra i za tip rezulatata u deniciji nekog metoda. Na primer, ako se ima u vidu gornja denicija klase Student, u programu se moe deklarisati promenljiva klasnog tipa Student:

58

2. K L ASE

Student s;

Kao to je to uobi cajeno, ovom naredbom se u memoriji ra cunara rezervie prostor za promenljivu s radi c uvanja njenih vrednosti tipa Student. Medutim, iako vrednosti tipa Student jesu objekti klase Student, promenljiva s ne sadri ove objekte. U stvari, u Javi vai opte pravilo da nijedna promenljiva nikad ne sadri neki objekat. Da bismo ovo razjasnili, moramo malo bolje razumeti postupak konstruisanja objekata. Objekti se konstruiu u specijalnom delu memorije programa koji se zove hip (engl. heap). Pri tome se, zbog brzine, ne vodi mnogo ra cuna o redu po kojem se zauzima taj deo memorije. (Re c heap na engleskom zna ci zbrkana gomila, hrpa.) To zna ci da se novi objekat smeta u hip memoriju tamo gde se nade prvo slobodno mesto, a i da se njegovo mesto prosto oslobada kada nije vie potreban u programu. Naravno, da bi se moglo pristupiti objektu u programu, mora se imati informacija o tome gde se on nalazi u hip memoriji. Ta infomacija se naziva referenca ili pokaziva c na objekat i svodi se na adresu memorijske lokacije objekta u hip memoriji. Promenljiva klasnog tipa dakle ne sadri neki objekat kao svoju vrednost, ve c referencu na taj objekat. Zato kada se u programu koristi promenljiva klasnog tipa, ona slui za indirektan pristup objektu na koga ukazuje aktuelna vrednost (referenca) te promenljive. Konstruisanje objekata svake klase u programu se izvodi posebnim operatorom c ija je oznaka slubena re c new. Pored konstruisanja jednog objekta, odnosno rezervisanja potrebnog memorijskog prostora u hipu, rezultat ovog operatora je i vra canje reference na novokonstruisani objekat. To je neophodno da bi se kasnije u programu moglo pristupiti novom objektu i s njim uraditi neto korisno. Zbog toga se vra cena referenca na novi objekat mora sa cuvati u promenljivoj klasnog tipa, jer ina ce se novom objektu nikako druga cije ne moe pristupiti. Na primer, ako je promenljiva s tipa Student deklarisana kao u prethodnom primeru, onda izvravanjem naredbe dodele
s = new Student();

najpre bi se konstruisao novi objekat koji je instanca klase Student, a zatim bi se referenca na njega dodelila promenljivoj s. Prema tome, vrednost promenljive s je referenca na novi objekat, a ne sam taj objekat. Nije zato sasvim ispravno kratko re ci da je taj objekat vrednost promenljive s (mada je ponekad teko izbe cu ovu kra cu terminologiju). Jo manje je ispravno re ci da promenljiva s sadri taj objekat. Ispravan na cin izraavanja je da promenljiva s ukazuje na novi objekat. (Ove ta cnije terminologije c emo se pridravati u tekstu sem ako to nije zaista preterano, kao na primer u slu caju stringova).

2.2. Promenljive klasnog tipa

59

Iz denicije klase Student moe se zaklju citi da novokonstruisani objekat klase Student, na koga ukazuje promenljiva s, sadri polja ime, test1, test2 i test3. U programu se ova polja tog konkretnog objekta mogu koristiti pomo cu standardne ta cka-notacije: s.ime, s.test1, s.test2 i s.test3. Tako, recimo, mogu se pisati slede ce naredbe:
System.out.println("Ocene studenta " + s.ime + " su:"); System.out.println("Prvi test: " + s.test1); System.out.println("Drugi test: " + s.test2); System.out.println("Tre ci test: " + s.test3);

Ovim naredbama bi se na ekranu prikazali ime i ocene studenta predstavljenog objektom na koga ukazuje promenljiva s. Optije, polja s.ime i, recimo, s.test1 mogu se koristiti u programu na svakom mestu gde je dozvoljena upotreba promenljivih tipa String odnosno double. Na primer, ako treba odrediti broj znakova stringa u polju s.ime, onda se moe koristiti zapis s.ime.length(). Sli cno, objektni metod prosek() se moe pozvati za objekat na koga ukazuje s zapisom s.prosek(). Da bi se prikazala prose cna ocena studenta na koga ukazuje s moe se pisati, na primer:
System.out.print("Prose cna ocene studenta " + s.ime); System.out.println(" je: " + s.prosek());

U nekim slu cajevima je potrebno nazna citi da promenljiva klasnog tipa ne ukazuje ni na jedan objekat. To se postie dodelom specijalne reference null promenljivoj klasnog tipa. Na primer, moe se pisati naredba dodele
s = null;

ili naredba grananja u obliku


if (s == null) ...

Ako je vrednost promenljive klasnog tipa jednaka null, onda ta promenljiva ne ukazuje ni na jedan objekat, pa se preko te promenljive ne mogu koristiti objektna polja i metodi odgovaraju ce klase. Tako, ako promenljiva s ima vrednost null, onda bi bila greka koristiti, recimo, s.test1 ili s.prosek(). Da bismo bolje razumeli vezu izmedu promenljivih klasnog tipa i objekata, razmotrimo detaljnije slede ci programski fragment:
Student s1, s2, s3, s4; s1 = new Student(); s2 = new Student(); s1.ime = "Pera Peri c"; s2.ime = "Mira Miri c"; s3 = s1; s4 = null;

60

2. K L ASE

Prvom naredbom u ovom programskom fragmentu se deklariu c etiri promenljive klasnog tipa Student. Drugom i tre com naredbom se konstruiu dva objekta klase Student i reference na njih se redom dodeljuju promenljivim s1 i s2. Narednim naredbama se vlastitom polju ime ovih novih objekata dodeljuju vrednosti odgovaraju cih stringova. (Podsetimo se da ostala polja konstruisanih objekata dobijaju podrazumevane vrednosti odmah nakon konstruisanja objekata.) Na kraju, pretposlednjom naredbom se vrednost promenljive s1 dodeljuje promenljivoj s3, a poslednjom naredbom se referenca null dodeljuje promenljivoj s4. Stanje memorije posle izvravanja svih naredbi u ovom primeru prikazano je na slici 2.1. Na toj slici su promenljive prikazana u obliku malih pravougaonika, a objekti u obliku ve cih pravougaonika sa zaobljenim uglovima. Ako promenljiva sadri referencu na neki objekat, vrednost te promenljive je prikazana u obliku strelice koja pokazuje na taj objekat. Ako promenljiva sadri referencu null, ona ne pokazuje ni na jedan objekat, pa je vrednost te promenljive prikazana kao podebljana ta cka.
s1 s2 s3 s4

objekat tipa Student


ime test1 test2 test3 prosek

objekat tipa Student


ime

0 0 0

test1 test2 test3 prosek

0 0 0

objekat tipa String


"Pera Peri c"

objekat tipa String


"Mira Miri c"

...

...

Slika 2.1: Veza izmedu promenljivih klasnog tipa i objekata.

Sa slike 2.1 se vidi da promenljive s1 i s3 ukazuju na isti objekat. Ta c injenica je posledica izvravanja pretposlednje naredbe dodele s3 = s1; kojom se kopira referenca iz s1 u s3. Obratite panju na to da ovo vai u op-

2.2. Promenljive klasnog tipa

61

tem slu caju: kada se jedna promenljiva klasnog tipa dodeljuje drugoj, onda se kopira samo referenca, ali ne i objekat na koga ta referenca ukazuje. To zna ci da je vrednost polja s1.ime posle izvravanja naredbi u prethodnom primeru jednaka stringu "Pera Peri c", ali i da je vrednost polja s3.ime jednaka "Pera Peri c". U Javi se jednakost ili nejednokost promenljivih klasnog tipa moe proveravati relacijskim operatorima == i !=, ali pri tome treba biti obazriv. Nastavljaju ci prethodni primer, ako se napie, recimo,
if (s1 == s3) ...

onda se, naravno, ispituje da li su vrednosti promenljivih s1 u s3 jednake. Ali te vrednosti su reference, tako da se time ispituje samo da li promenljive s1 u s3 ukazuju na isti objekat, ali ne i da li su jednaki objekti na koje ove promenljive ukazuju. To je u nekim slu cajevima ba ono to je potrebno, ali ako treba ispitati jednakost samih objekata na koje promenljive ukazuju, onda se mora pojedina cno ispitati jednakost svih polja tih objekata. Drugim re cima, za objekte na koje ukazuju promenljive s1 u s3 treba ispitati da li je ta can slede ci uslov:
s1.ime.equals(s3.ime) && (s1.test1 == s3.test1) && (s1.test2 == s3.test2) && (s1.test3 == s3.test3)

Kako su stringovi zapravo objekti klase String, a ne primitivne vrednosti, na slici 2.1 su stringovi "Pera Peri c" i "Mira Miri c" prikazani kao objekti. Neka promenljiva klasnog tipa String moe dakle sadrati samo referencu na string (kao i referencu null), a ne i sm niz znakova koji c ine string. To objanjava zato su na slici 2.1 vrednosti dva primerka polja ime tipa String prikazane strelicama koje pokazuju na objekte odgovaraju cih stringova. Primetimo da se u vezi sa stringovima c esto primenjuje neprecizna objektna terminologija, iako su stringovi pravi objekti kao i svaki drugi u Javi. Tako, na primer, kaemo da je vrednost polja s1.ime ba string "Pera Peri c", a ne pravilno ali rogobatnije da je vrednost polja s1.ime referenca na objekat stringa "Pera Peri c". Spomenimo na kraju jo dve logi cne posledice c injenice da promenljive klasnog tipa sadre reference na objekte, a ne same objekte. (Istaknimo jo jednom to vano pravilo: objekti se zi cki nalaze negde u hip memoriji, a promenljive klasnog tipa samo ukazuju na njih.) Prvo, pretpostavimo da je promenljiva klasnog tipa deklarisana s modikatorom final. To zna ci da se njena vrednost nakon inicijalizacije vie ne moe promeniti. Ta po cetna, nepromenljiva vrednost ovakve promenljive klasnog tipa je referenca na neki objekat, to zna ci da c e ona ukazivati na njega za sve vreme svog postojanja. Medutim, to nas ne spre cava da promenimo vrednosti polja koja se nalaze u

62

2. K L ASE

objektu na koji ukazuje final promenljiva klasnog tipa. Drugim re cima, vrednost final promenljive je konstantna referenca, a nije konstantan objekat na koji ta promenljiva ukazuje. Ispravno je zato pisati, na primer:
final Student s = new Student(); // vrednost polja ime je: // s.ime = null s.ime = "Laza Lazi c"; // promena polja ime, // a ne promenljive s

Drugo, pretpostavimo da se vrednost promenljive klasnog tipa x prenosi kao argument prilikom poziva nekog metoda. Onda se, kao to je poznato, vrednost promenljive x dodeljuje odgovaraju cem parametru metoda i metod se izvrava. Vrednost promenljive x se ne moe promeniti izvravanjem metoda, ali poto dodeljena vrednost odgovaraju cem parametru predstavlja referencu na neki objekat, u metodu se mogu promeniti podaci u tom objektu. Druga cije re ceno, nakon izvravanja metoda, promenljiva x c e i dalje ukazivati na isti objekat, ali podaci u tom objektu mogu biti promenjeni. Konkretnije, pod pretpostavkom da je denisan metod
void promeni(Student s) { s.ime = "Mika Miki c"; }

i da se izvrava ovoj programski fragment


Student x = new Student(); x.ime = "Pera Peri c"; promeni(x); System.out.println(x.ime);

nakon njegovog izvravanja se na ekranu prikazuje string "Mika Miki c" kao vrednost polja x.ime, a ne "Pera Peri c". Naime, izvravanje naredbe poziva promeni(x) ekvivalentno je izvravanju dve naredbe dodele
s = x; s.ime = "Mika Miki c";

odnosno polje ime objekta na koji ukazuje promenljiva s, a time i x, dobi ce novu vrednost.

2.3 Konstrukcija i inicijalizacija objekata


Klasni tipovi u Javi su vrlo razli citi od primitivnih tipova. Vrednosti primitivnih tipova su ugradene u jezik, dok se vrednosti klasnih tipova, koje pred stavljaju objekte odgovaraju ce klase, moraju u programu eksplicitno konstruisati. Primetimo da, s druge strane, ne postoji sutinska razlika u postupanju

2.3. Konstrukcija i inicijalizacija objekata

63

sa promenljivim primitivnih i klasnih tipova, jer se razlikuju samo vrednosti koje mogu biti njihov sadraj kod jednih su to primitivne vrednosti, a kod drugih su to reference. Postupak konstruisanja jednog objekta se sastoji od dva koraka: rezervisanja dovoljno mesta u hip memoriji za smetanje objekta i inicijalizacije njegovih polja podrazumevanim vrednostima. Programer ne moe da uti ce na prvi korak pronalaenja mesta u memoriji za smetanje objekta, ali za drugi korak u sloenijem programu obi cno nije dovoljna samo podrazumevana inicijalizacija. Podsetimo se da se ova automatska inicijalizacija sastoji od dodele vrednosti nula poljima numeri ckog tipa (int, double, . . . ), dodele vrednosti false logi ckim poljima, dodele Unicode znaka \u0000 znakovnim poljima i dodele reference null poljima klasnog tipa. Ukoliko podrazumevana inicijalizacija nije dovoljna, poljima se mogu dodeliti po cetne vrednosti u njihovim deklaracijama, ba kao to se to moe uraditi za bilo koje druge promenljive. Radi konkretnosti, pretpostavimo da u programu za simuliranje neke drutvene igre treba predstaviti kocku za bacanje. U nastavku je prikazana klasa KockaZaIgru c iji su objekti ra cunarske reprezentacije realnih objekata kocki za bacanje u drutvenim igrama. Ova klasa sadri jedno objektno polje c iji sadraj odgovara broju palom pri bacanju kocke i jedan objektni metod kojim se simulira slu cajno bacanje kocke.
public class KockaZaIgru { public int broj = 6; // broj koji je pao

public void baci() { // bacanje kocke broj = (int)(Math.random()*6) + 1; } }

U ovom primeru klase KockaZaIgru, polje broj dobija po cetnu vrednost 6 svaki put kada se konstruie objekat klase KockaZaIgru. Poto se u programu moe konstruisati vie objekata klase KockaZaIgru, svi oni c e imati svoj primerak polja broj i svi oni dobijaju vrednost 6 na po cetku. Drugi primer je moda interesantniji, jer je po cetni broj kocke za igru slu cajan:
public class KockaZaIgru { public int broj = (int)(Math.random()*6) + 1; public void baci() { broj = (int)(Math.random()*6) + 1;

64

2. K L ASE

} }

U ovom primeru se polje broj inicijalizuje slu cajnom vredno cu. Kako se inicijalizacija obavlja za svaki novokonstruisani objekat, razli citi objekti druge verzije klase KockaZaIgru ima ce verovatno razli cite po cetne vrednosti svojih primeraka polja broj. Inicijalizacija stati ckih polja neke klase nije mnogo druga cija od inicijalizacije nestati ckih (objektnih) polja, osim to treba imati u vidu da stati cka polja nisu vezana za pojedina cne objekte nego za klasu kao celinu. Kako postoji samo jedan primerak stati ckog polja klase, ono se inicijalizuje samo jednom i to kada se klasa po prvi put u citava od strane JVM. Posvetimo sada vie panje detaljima konstrukcije objekata u Javi. Konstruisanje objekata u programu se obavlja posebnim operatorom new. Na primer, u programu koji koristi klasu KockaZaIgru moe se pisati:
KockaZaIgru kocka; // deklaracija promenljive // klasnog tipa KockaZaIgru kocka = new KockaZaIgru(); // konstruisanje objekta klase // KockaZaIgru i dodela njegove // reference toj promenljivoj

Isti efekat se moe postiti kra cim zapisom:


KockaZaIgru kocka = new KockaZaIgru();

U ovim primerima se izrazom new KockaZaIgru() na desnoj strani znaka jednakosti konstruie novi objekat klase KockaZaIgru, inicijalizuju se njegova objektna polja i vra ca se referenca na taj novi objekat kao rezultat tog izraza. Ova referenca se zatim dodeljuje promenljivoj kocka na levoj strani znaka jednakosti, tako da na kraju promenljiva kocka ukazuje na novokonstruisani objekat. Primetimo da deo KockaZaIgru() iza operatora new podse ca na poziv metoda, to zapravo to i jeste. Naime, to je poziv specijalnog metoda koji se naziva konstruktor klase. Ovo je moda malo iznenaduju ce, jer se u de niciji klase KockaZaIgru ne nalazi takav metod. Medutim, svaka klasa ima bar jedan konstruktor, koji se automatski dodaje ukoliko nije eksplicitno denisan nijedan drugi konstruktor. Taj podrazumevani konstruktor ima samo formalnu funkciju i prakti cno ne radi nita. Ali kako se konstruktor poziva odmah nakon konstruisanja objekta i inicijalizacije njegovih polja podrazumevanim vrednostima, u klasi se moe denisati jedan ili vie posebnih konstruktora u klasi radi dodatne inicijalizacije novih objekta. Denicija konstruktora klase se pie na isti na cin kao denicija obi cnog metoda, uz tri razlike:

2.3. Konstrukcija i inicijalizacija objekata

65

1. Ime konstruktora mora biti isto kao ime klase u kojoj se denie. 2. Jedini dozvoljeni modikatori su public, private i protected. 3. Konstruktor nema tip rezultata, c ak ni void. S druge strane, konstruktor sadri uobi cajeno telo metoda u formi bloka naredbi unutar kojeg se mogu pisati bilo koje naredbe. Isto tako, konstruktor moe imati parametre kojima se mogu preneti vrednosti za inicijalizaciju objekata nakon njihove konstrukcije. U tre coj verziji klase KockaZaIgru je denisan poseban konstruktor koji ima parametar za po cetnu vrednost broja koji pokazuje kocka:
public class KockaZaIgru { public int broj; public KockaZaIgru(int n) { broj = n; } // broj koji je pao // konstruktor

public void baci() { // bacanje kocke broj = (int)(Math.random()*6) + 1; } }

U ovom primeru klase KockaZaIgru, konstruktor


public KockaZaIgru(int n) { broj = n; }

ima isto ime kao klasa, nema tip rezultata i ima jedan parametar. Poziv konstruktora, sa odgovaraju cim argumentima, navodi se iza operatora new. Na primer:
KockaZaIgru kocka = new KockaZaIgru(1);

Na desnoj strani znaka jednakosti ove naredbe deklaracije promenljive kocka konstruie se novi objekat klase KockaZaIgru, poziva se konstruktor te klase kojim se polje broj novokonstruisanog objekta inicijalizuje vredno cu argumenta 1 i, na kraju, referenca na taj objekat se dodeljuje promenljivoj kocka. Primetimo da se podrazumevani konstruktor dodaje klasi samo ukoliko nije eksplicitno denisan nijedan drugi konstruktor u klasi. Zato, na primer, izrazom new KockaZaIgru() ne bi vie mogao da se konstruie objekat prethodne klase KockaZaIgru. Ali to i nije veliki problem, jer se konstruktori mogu preoptere civati kao i obi cni metodi. To zna ci da klasa moe imati vie konstruktora pod uslovom, naravno, da su njihovi potpisi razli citi. Na primer,

66

2. K L ASE

prethodnoj klasi KockaZaIgru moe se dodati konstruktor bez parametara koji polju broj konstruisanog objekta po cetno dodeljuje slu cajan broj:
public class KockaZaIgru { public int broj; public KockaZaIgru() { baci(); } public KockaZaIgru(int n) { broj = n; } // broj koji je pao // konstruktor bez parametara // poziv metoda baci()

// konstruktor sa parametrom

public void baci() { // bacanje kocke broj = (int)(Math.random()*6) + 1; } }

Objekti ove klase KockaZaIgru se mogu konstruisati na dva na cina: bilo izrazom new KockaZaIgru() ili izrazom new KockaZaIgru(x), gde je x izraz tipa int. Na osnovu svega do sada re cenog moe se zaklju citi da su konstruktori specijalna vrsta metoda. Oni nisu objektni metodi jer ne pripadaju objektima, ve c se pozivaju samo u trenutku konstruisanja objekata. Ali oni nisu ni stati cki metodi klase, jer se za njih ne moe koristiti modikator static. Za razliku od drugih metoda, konstruktori se mogu pozvati samo uz operator new u izrazu oblika:
new ime-klase( lista-argumenata )

Rezultat ovog izraza je referenca na konstruisani objekat, koja se naj ce ce odmah dodeljuje promenljivoj klasnog tipa u naredbi dodele. Medutim, ovaj izraz se moe pisati svuda gde to ima smisla, na primer kao neki argument u pozivu metoda ili kao deo nekog drugog ve ceg izraza. Zbog toga je vano razumeti ta can postupak izra cunavanja ovog izraza, jer je njegov efekat zapravo rezultat izvravanja redom ova c etiri koraka: 1. Pronalazi se dovoljno veliki blok slobodne hip memorije za objekat navedene klase koji se konstruie. 2. Inicijalizuju se objektna polja tog objekta. Po cetna vrednost nekog polja objekta je ili ona izra cunata iz deklaracije tog polja u klasi ili podrazumevana vrednost predvidena za njegov tip.

2.3. Konstrukcija i inicijalizacija objekata

67

3. Poziva se konstruktor klase na uobi cajen na cin: najpre se eventualni argumenti u pozivu konstruktora izra cunavaju i dodeljuju odgovaraju cim parametrima konstruktora, a zatim se izvravaju naredbe u telu konstruktora. 4. Referenca na konstruisani objekat se vra ca kao rezultat izraza. Referenca na konstruisani objekat, koja je dobijena na kraju ovog postupka, moe se zatim koristiti u programu za pristup poljima i metodima novog objekta.

Primer: broj bacanja dve kocke dok se ne pokau isti brojevi


Jedna od prednosti objektno orijentisanog programiranja je mogu cnost viekratne upotrebe programskog koda. To zna ci da, recimo, klase napisane jednom mogu se iskoristiti u razli citim programima u kojima je potrebna njihova funkcionalnost. Na primer, poslednja klasa KockaZaIgru predstavlja zi cke kocke za igranje i obuhvata sve njihove relevantne atribute i mogu cnosti. Zato se ta klasa moe koristi u svakom programu c ija se logika zasniva na kockama za igranje. To je velika prednost, pogotovo za komplikovane klase, jer nije potrebno gubiti vreme na ponovno pisanje i testiranje novih klasa. Da bismo ilustrovali ovu mogu cnost, u nastavku je prikazan program koji koristi klasu KockaZaIgru za odredivanje broja puta koliko treba baciti dve kocke pre nego to se dobije isti broj na njima.
public class BacanjaDveKocke { public static void main(String[] args) { int brojBacanja = 0; // broja c bacanja dve kocke KockaZaIgru kocka1 = new KockaZaIgru(); // prva kocka KockaZaIgru kocka2 = new KockaZaIgru(); // druga kocka do { kocka1.baci(); System.out.print("Na prvoj kocki je pao broj: "); System.out.println(kocka1.broj); kocka2.baci(); System.out.print("Na drugoj kocki je pao broj: "); System.out.println(kocka2.broj); brojBacanja++; // ura cunati bacanje

} while (kocka1.broj != kocka2.broj);

68

2. K L ASE

System.out.print("Dve kocke su ba cene " + brojBacanja); System.out.println(" puta pre nego to je pao isti broj."); } }

2.4 Uklanjanje objekata


Novi objekat se konstruie operatorom new i (dodatno) inicijalizuje konstruktorom klase. Od tog trenutka se objekat nalazi u hip memoriji i moe mu se pristupiti preko promenljivih koje sadre referencu na njega. Ako nakon izvesnog vremena objekat vie nije potreban u programu, postavlja se pitanje da li se on moe ukloniti? Odnosno, da li se radi utede memorije moe osloboditi memorija koju objekat zauzima? U nekim jezicima sm programer mora voditi ra cuna o uklanjanju objekata i u tim jezicima su predvideni posebni na cini kojima se eksplicitno ukla nja objekat u programu. U Javi, uklanjanje objekata se dogada automatski i programer je osloboden obaveze da brine o tome. Osnovni kriterijum na osnovu kojeg se u Javi prepoznaje da objekat nije vie potreban je da ne postoji vie nijedna promenljiva koja ukazuje na njega. To ima smisla, jer se takvom objektu vie ne moe pristupiti u programu, to je isto kao da ne postoji, pa bespotrebno zauzima memoriju. Da bismo ovo ilustrovali, posmatrajmo slede ci metod (primer je dodue veta cki, jer tako neto ne treba pisati u pravom programu):
void novi() { Student s = new Student(); s.ime = "Pera Peri c"; . . . }

U metodu novi() se konstruie objekat klase Student i referenca na njega se dodeljuje lokalnoj promenljivoj s. Ali nakon izvravanja poziva metoda novi(), njegova lokalna promenljiva s se dealocira tako da vie ne postoji referenca na objekat konstruisan u metodu novi(). Vie dakle nema na cina da se tom objektu pristupi u programu, pa se objekat moe ukloniti i memorija koju zauzima osloboditi za druge namene. I sm programer moe ukazati da neki objekat nije vie potreban u programu. Na primer:
Student s = new Student(); . . . s = null; . . .

2.5. Skrivanje podataka i enkapsulacija

69

U ovom primeru, nakon konstruisanja jednog objekta klase Student i njegovog kori cenja preko promenljive s, toj promenljivoj se eksplicitno dodeljuje referenca null kada taj objekat nije vie potreban. Time se efektivno gubi veza s tim objektom, pa se njemu vie ne moe pristupiti u programu. Objekti koji se nalaze u hip memoriji, ali se u programu vie ne mogu koristiti jer nijedna promenljiva ne sadri referencu na njih, popularno se nazivaju otpaci. U Javi se koristi posebna procedura sakupljanja otpadaka (engl. garbage collection) kojom se automatski s vremena na vreme cisti du bre, odnosno oslobada memorija onih objekata za koje se nade da je broj referenci na njih u programu jednak nuli. U prethodna dva primera se moe vrlo lako otkriti kada jedan objekat klase Student nije dostupan i kada se moe ukloniti. U sloenim programima je to obi cno mnogo tee. Ako je neki objekat bio kori cen u programu izvesno vreme, onda je mogu ce da vie promenljvih sadre referencu na njega. Taj objekat nije otpadak sve dok postoji bar jedna promenljiva koja ukazuje na njega. Drugi komplikovaniji slu caj je kada grupa objekata obrazuje lanac referenci izmedu sebe, a ne postoji promenljiva izvan tog lanca sa referencom na neki objekat u lancu. Sre com, procedura sakupljanja otpadaka moe sve ovo prepoznati i zato su Java programeri u velikoj meri oslobodeni odgovornosti od racionalnog upravljanja memorijom uklanjanjem nepotrebnih objekata u programu. Iako procedura sakupljanja otpadaka usporava izvravanje samog programa, razlog zato se u Javi uklanjanje objekata obavlja automatski, a ne kao u nekim jezicima ru cno od strane programera, jeste to to je vodenje ra cuna o tome u sloenijim programima vrlo teko i podlono grekama. Prva vrsta c estih greaka se javlja kada se nenamerno obrie neki objekat, mada i dalje postoje reference na njega. Ove greke vise cih pokaziva ca dovode do problema pristupa nedozvoljenim delovima memorije. Druga vrsta greaka se javlja kada programer zanemari da ukloni nepotrebne objekte. Tada dolazi do greke curenja memorije koja se manifestuje time da program zauzima veliki deo memorije, iako je ona prakti cno neiskori cena. To onda dovodi do problema nemogu cnosti izvravanja istog ili drugih programa zbog nedostatka memorije.

2.5 Skrivanje podataka i enkapsulacija


Do sada je malo panje bilo posve ceno kontroli pristupa c lanovima neke klase. U dosadanjim primerima su c lanovi klase uglavnom bili deklarisani s modikatorom public kako bi bili dostupni iz bilo koje druge klase. Me-

70

2. K L ASE

dutim, vano objektno orijentisano na celo enkapsulacije (ili u caurivanja) na lae da sva objektna polja klase budu skrivena i deklarisana s modikatorom private. Pri tome, pristup vrednostima tih polja iz drugih klasa treba omogu citi samo preko javnih metoda. Jedna od prednosti ovog pristupa je to to su na taj na cin sva polja (i interni metodi) klase bezbedno zati ceni unutar caure klase i mogu se menjati samo na kontrolisan na cin. To umnogome olakava testiranje i pronalaenje greaka. Ali moda vanija korist je to to se time mogu sakriti interni implementacioni detalji klase. Ako drugi programeri ne mogu koristiti te detalje, ve c samo elemente dobro denisanog interfejsa klase, onda se implementacija klase moe lako promeniti usled novih okolnosti. To zna ci da je dovoljno zadrati samo iste elemente starog interfejsa u novoj implementaciji, jer se time ne ce naruiti ispravnost nekog programa koji koristi prethodnu implementaciju klase. Da bismo ove optu diskusiju u cinili konkretnijom, razmotrimo primer jedne klase koja predstavlja radnike neke rme, recimo, radi obra cuna plata:
public class Radnik { // Privatna polja private String ime; private long jmbg; private int sta; private double plata; // Konstruktor public Radnik(String i, long id, int s, double p) { ime = i; jmbg = id; sta = s; plata = p; } // Javni interfejs public String getIme() { return ime; } public long getJmbg() { return jmbg; } public int getSta() { return sta; }

2.5. Skrivanje podataka i enkapsulacija

71

public void setSta(int s) { sta = s; } public double getPlata() { return plata; } public void pove cajPlatu(double procenat) { plata += plata * procenat / 100; } }

Obratite panju na to da su sva objektna polja klase Radnik deklarisana da budu privatna. Time je obezbedeno da se ona izvan same klase Radnik ne mogu direktno koristiti. Tako, u nekoj drugoj klasi se vie ne moe pisati, na primer:
Radnik r = new Radnik("Pera Peri c", 111111, 3, 1000); System.out.println(r.ime); // GREKA: ime je privatno polje r.sta = 17; // GREKA: sta je privatno polje

Naravno, vrednosti polja za konkretnog radnika se u drugim klasama programa moraju koristiti na neki na cin, pa su u tu svrhu denisani javni metodi sa imenima koja po cinju re cima get i set. Ovi metodi su deo javnog interfejsa klase i zato se u drugoj klasi moe pisati:
Radnik r = new Radnik("Pera Peri c", 111111, 3, 1000); System.out.println(r.getIme()); r.setSta(17);

Metodi c ija imena po cinju sa get samo vra caju vrednosti objektnih polja i nazivaju se geteri. Metodi c ija imena po cinju sa set menjaju sadraj objektnih polja i nazivaju se seteri (ili mutatori). Naalost, ova terminologija nije u duhu srpskog jezika, ali je uobi cajena usled nedostatka makar priblino dobrog prevoda. Cak i da postoje bolji srpski izrazi, konvencija je da se ime geter-metoda za neku promenljivu pravi dodavanjem re ci get ispred kapitalizovanog imena te promenljive. Tako, za promenljivu ime se dobija getIme, za promenljivu jmbg se dobija getJmbg i tako dalje. Ime geter-metoda za logi cko polje se dozvoljava da po cinje i sa is. Tako, da u klasi Radnik postoji polje vredan tipa boolean, njegov geter-metod bi se mogao zvati isVredan() umesto getVredan(). Konvencija za ime seter-metoda za neku promenljivu je da se ono pravi dodavanjam re ci set ispred kapitalizovanog imena te promenljive. Zato je za promenljivu sta u klasi Radnik denisan seter-metod setSta().

72

2. K L ASE

Ovo meanje engleskih re ci get, set i is sa mogu ce srpskim imenima promenljivih dodatno doprinosi rogobatnosti tehnike skrivanja podataka. S druge strane, neki aspekti naprednog Java programiranja se potpuno zasnivaju na prethodnoj konvenciji za imena getera i setera. Na primer, tehnologija programskih komponenti JavaBeans podrazumeva da geter ili seter metodi u klasi deniu takozvano svojstvo klase (koje c ak ni ne mora odgovarati polju). Zbog toga se preporu cuje da se programeri pridravaju konvencije za imena getera i setera, kako bi se olakalo eventualno prilagodavanje klase napred nim tehnikama. Da li se neto dobija deklarisanjem polja ime, jmbg, sta i plata da budu privatna na ra cun dodatnog pisanja nekoliko naizgled nepotrebnih metoda u klasi Radnik? Zar nije jednostavnije da su sva ova polja samo deklarisana da budu javna i tako bi se izbegle dodatne komplikacije? Prvo to se dobija za dodatno uloeni trud je lake otkrivanje greaka. Polja ime i jmbg se ne mogu nikako menjati nakon po cetne dodele vrednosti u konstruktoru, c ime je osigurano to da se u nijednom delu programa izvan klase Radnik ne moe (slu cajno ili namerno) ovim poljima dodeliti neka nekonzistentna vrednost. Vrednosti polja sta i plata se mogu menjati, ali samo na konrolisan na cin metodima setSta() i pove cajPlatu(). Prema tome, ako su vrednosti tih polja na neki na cin pogrene, jedini uzro cnici mogu biti ovi metodi, pa je zato veoma olakano traenje greke. Da su polja sta i plata bila javna, onda bi se izvor greke mogao nalaziti bilo gde u programu. Druga korist je to to geter i seter metodi nisu ograni ceni samo na c itanje i upisivanje vrednosti odgovaraju cih polja. Ova mogu cnost se moe iskoristiti tako da se, na primer, u geter-metodu prati (broji) koliko puta se pristupa nekom polju:
public double getPlata() { plataBrojPristupa++; return plata; }

Sli cno, u seter-metodu se moe obezbediti da dodeljene vrednosti polju budu samo one koje su smislene:
public void setSta(int s) { if (s < 0) { System.out.println("Greka: sta je negativan"); System.exit(-1); } else sta = s; }

2.6. Slubena re c this

73

Tre ca korist od skrivanja podataka je to to se moe promeniti interna implementacija neke klase bez posledica na programe koji koriste tu klasu. Na primer, ako predstavljanje punog imena radnika mora da se razdvoji u dva posebna dela za ime i prezime, onda je dovoljno klasi Radnik dodati jo jedno polje za prezime, recimo,
private String prezime;

i promeniti geter-metod getIme() tako da se ime radnika formira od dva dela:


public String getIme() { return ime + " " + prezime; }

Ove promene su potpuno nevidljive za programe koji koriste klasu Radnik i ti programi se ne moraju uopte menjati ( cak ni ponovo prevoditi) da bi ispravno radili kao ranije. U optem slu caju, geteri i seteri, pa i ostali metodi, verovatno moraju pretrpeti velike izmene radi prelaska sa stare na novu implementaciju klase. Ali poenta enkapsulacije je da stari programi koji koriste novu verziju klase ne moraju uopte da se menjaju.

2.6 Slubena re c this


Objektni metodi neke klase se primenjuju na pojedine objekte te klase i ti metodi tokom svog izvravanja koriste konkretne vrednosti polja onih objekata za koje su pozvani. Na primer, objektni metod pove cajPlatu() klase Radnik iz prethodnog odeljka
public void pove cajPlatu(double procenat) { plata += plata * procenat / 100; }

dodeljuje novu vrednost polju plata koje pripada objektu za koji se ovaj metod pozove. Efekat poziva, recimo,
agent007.pove cajPlatu(10);

sastoji se u pove canju vrednosti polja plata za 10% onog objekta na koji ukazuje promenljiva agent007. Drugim re cima, taj efekat je ekvivalnetan izvravanju naredbe dodele:
agent007.plata += agent007.plata * 10 / 100;

Poziv objektnog metoda pove cajPlatu() sadri dva argumenta. Prvi, implicitni argument se nalazi ispred imena metoda i ukazuje na objekat klase Radnik za koji se metod poziva. Drugi, eksplicitni argument se nalazi iza imena metoda u zagradama.

74

2. K L ASE

Poznato je da se parametri koji odgovaraju eksplicitnim argumentima poziva metoda navode u deniciji metoda. S druge strane, parametar koji odgovara implicitnom argumentu se ne navodi u deniciji metoda, ali se moe koristiti u svakom metodu. Njegova oznaka u Javi je slubena re c this. U ovom slu caju dakle, re c this ozna cava promenljivu klasnog tipa koja u trenutku poziva metoda dobija vrednost reference na objekat za koji je metod pozvan. To zna ci da se, recimo, u metodu pove cajPlatu() moe pisati:
public void pove cajPlatu(double procenat) { this.plata += this.plata * procenat / 100; }

Primetimo da je ovde re c this uz polje plata nepotrebna i da se podrazumeva. Ipak, neki programeri uvek koriste ovaj stil pisanja, jer se tako jasno razlikuju objektna polja klase od lokalnih promenljivih metoda. Prilikom poziva konstruktora, implicitni parametar this ukazuje na objekat koji se konstruie. Zbog toga se parametrima konstruktora c esto daju ista imena koja imaju objektna polja klase, a u telu konstruktora se ta polja piu sa preksom this. Na primer:
public Radnik(String ime, long jmbg, int sta, double plata) { this.ime = ime; this.jmbg = jmbg; this.sta = sta; this.plata = plata; }

Prednost ovog stila pisanja je to to ne moraju da se smiljaju dobra imena za parametre konstruktora kako bi se jasno ukazalo ta svaki od njih zna ci. Primetimo da se u telu konstruktora moraju pisati puna imena polja sa preksom this, jer nema smisla pisati naredbu dodele, recimo, ime = ime. U stvari, to bi bilo pogreno, jer se ime u telu konstruktoru odnosi na parametar konstruktora. Zato bi se naredbom ime = ime vrednost parametra ime opet dodelila njemu samom, a objektno polje ime bi ostalo netaknuto. U prethodnim primerima nije bilo neophodno koristiti promenljivu this. U prvom primeru se ona implicitno dodaje, pa je njeno isticanje vie odlika li cnog stila. U drugom primeru se moe izbe ci njena upotreba davanjem imena parametrima konstruktora koja su razli cita od onih koja imaju objektna polja. Ipak, u nekim slu cajevima je promenljiva this neophodna i bez nje se ne moe dobiti eljena funkcionalnost. Da bismo to pokazali, pretpostavimo da je klasi Radnik potrebno dodati objektni metod kojim se uporeduju dva radnika na osnovu njihovih plata. Ta cnije, treba napisati metod ve ceOd() tako da se pozivom tog metoda u obliku, na primer,

2.6. Slubena re c this

75

pera.ve ceOd(ika)

kao rezultat dobija referenca na onaj objekat, od dva data objekta na koje ukazuju promenljive pera i ika, koji ima ve cu platu. Ovaj metod mora koristiti promenljivu this, jer rezultat metoda moe biti implicitni parametar metoda:
public Radnik ve ceOd(Radnik drugi) { if (this.getPlata() > drugi.getPlata()) return this; else return drugi; }

Obratite ovde panju na to da se u zapisu this.getPlata() moe izostaviti promenljiva this, jer se bez nje poziv metoda getPlata() ionako odnosi na implicitni parametar metoda ve ceOd() ozna cen promenljivom this. S druge strane, promenljiva this jeste obavezna u naredbi return this u prvoj grani if naredbe, jer je rezultat metoda ve ceOd() u toj grani ba implicitni parametar ovog metoda. cije zna cenje od prethodSlubena re c this ima jo jedno, potpuno druga nog. Naime, konstruktor klase je obi can metod koji se moe preoptere civati, pa je mogu ce imati vie konstruktora sa razli citim potpisom u istoj klasi. U tom slu caju se razli citi konstruktori mogu medusobno pozivati, ali se poziv jednog konstruktora unutar drugog pie u obliku:
this( lista-argumenata );

Na primer, ukoliko klasi Radnik treba dodati jo jedan konstruktor kojim se konstruie pripravnik bez radnog staa i sa ksnom platom, to se moe uraditi na standardan na cin:
public Radnik(String ime, long jmbg) { this.ime = ime; this.jmbg = jmbg; this.sta = 0; this.plata = 100; }

Ali umesto toga, novi konstruktor se moe kra ce pisati:


public Radnik(String ime, long jmbg) { this(ime, jmbg, 0, 100); }

Ovde je zapisom
this(ime, jmbg, 0, 100);

76

2. K L ASE

ozna cen poziv prvobitnog konstruktora klase Radnik sa c etiri parametra. Izvravanje tog metoda sa navedenim argumentima je ekvivalentno ba onome to treba uraditi. Prednost primene slubene re ci this u ovom kontekstu je dakle to to se zajedni cke naredbe za konstruisanje objekata mogu pisati samo na jednom mestu u najoptijem konstruktoru. Onda se pozivom tog konstruktora pomo cu this sa odgovaraju cim argumentima mogu obezbediti drugi konstruktori za konstruisanje speci cnijih objekata. Pri tome treba imati u vidu i jedno ograni cenje: poziv nekog konstruktora pomo cu this mora biti prva naredba u drugom konstruktoru. Zato nije ispravno pisati
public Radnik(String ime, long jmbg) { System.out.println("Konstruisan pripravnik ... "); this(ime, jmbg, 0, 100); }

ali jeste ispravno:


public Radnik(String ime, long jmbg) { this(ime, jmbg, 0, 100); System.out.println("Konstruisan pripravnik ... "); }

G LAVA 3

N IZOVI

Osnovna jedinica za c uvanje podataka u programu je promenljiva. Medu tim, jedna promenljiva u svakom trenutku moe c uvati samo jedan podatak. Ako je u programu potrebno istovremeno imati na raspolaganju vie srodnih podataka koji c ine jednu celinu, onda se u Javi oni mogu organizovati u formi objekta koji se sastoji samo od polja, bez metoda. Takva forma organizacije podataka se u drugim jezicima naziva slog. To medutim ima smisla samo za mali broj podataka, jer denisanje velikog broja polja nije lako izvodljivo zbog toga to sva polja moraju imati razli cita imena. Istovremeno raspolaganje velikim ( cak neograni cenim) brojem podataka zahteva poseban na cin rada s njima koji omogu cava relativno lako dodavanje, uklanjanje, pretraivanje i sli cne operacije s pojedina cnim podacima. Ako se ima u vidu kolekcija podataka organizovanih u ovom smislu, onda se govori o strukturi podataka. U ovom poglavlju se govori o najosnovnijoj strukturi podataka u Javi koja se naziva niz.

3.1 Jednodimenzionalni nizovi


Niz je struktura podataka koja predstavlja numerisan niz promenljivih istog tipa. Pojedina cne promenljive u nizu se nazivaju elementi niza, a njihov redni broj u nizu se naziva indeks. Ukupan broj elemenata niza se naziva duina niza. U Javi, numeracija elemenata niza po cinje od nule. To zna ci da ako je n duina niza, onda indeks nekog elementa niza moe biti u intervalu od 0 do n 1. Svi elementi niza moraju biti istog tipa koji se naziva bazni tip niza. Za bazni tip elemenata niza nema ograni cenja to moe biti bilo koji tip u Javi, primitivni ili klasni. Niz elemenata (promenljivih) kao celina se u programu predstavlja preko jedne promenljive specijalnog, nizovnog tipa. Radi jednostavnijeg izraavanja, c esto se sama ova promenljiva naziva niz, mada je preciznije re ci pro-

78

3. N IZOVI

menljiva koja ukazuje na niz elemenata. Za ozna cavanje bilo kog elementa niza se koristi zapis koji se sastoji od imena te promenljive i indeksa odgovaraju ceg elementa u uglastim (srednjim) zagradama. Na primer, niz a od 100 elemenata se sastoji od 100 promenljivih istog tipa c iji je konceptualni izgled prikazan na slici 3.1.
a

a[0]

a[1]

a[2]

a[99]

Slika 3.1: Niz a od 100 elemenata.. Svaki element niza je obi cna promenljiva baznog tipa niza i moe imati bilo koju vrednost baznog tipa. Na primer, ako je bazni tip niza a na slici 3.1 deklarisan da bude int, onda je svaka od promenljivih a[0], a[1], . . . , a[99] obi cna celobrojna promenljiva koja se u programu moe koristiti na svakom mestu gde su dozvoljene celobrojne promenljive.

Denisanje nizova
U Javi je koncept niza elemenata realizovan na objektno orijentisan na cin: nizovi se u Javi smatraju specijalnom vrstom objekata. Pojedina cni elementi niza su, u sutini, polja unutar objekta niza, s tim to se ona ne ozna cavaju svojim imenima nego indeksima. Posebnost nizova u Javi se ogleda i u tome to, mada kao objekti moraju pripadati nekoj klasi, njihova klasa ne mora da se denie u programu. Naime, svakom postoje cem tipu T se automatski pridruuje klasa nizova koja se ozna cava T[]. Ova klasa T[] je upravo ona kojoj pripada objekat niza c iji su pojedina cni elementi tipa T. Tako, na primer, tipu int odgovara klasa int[] c iji su objekti svi nizovi baznog tipa int. Ili, ako je u programu denisana klasa Student, onda je automatski raspoloiva i klasa Student[] kojoj pripadaju svi objekti nizova baznog tipa Student. Za zapis T[] se koriste kra ci termini niz baznog tipa T ili niz tipa T. Primetimo da su strogo govore ci ti termini neprecizni, jer se onda nizom naziva i klasa i objekat. Nadamo se ipak da, posle po cetnog upoznavanja, ova dvosmislenost ne ce izazvati konfuziju kod c italaca. Postojanje nekog klasnog tipa niza, recimo int[], omogu cava da se deklarie neka promenljiva tog klasnog tipa. Na primer:
int[] a;

3.1. Jednodimenzionalni nizovi

79

Kao i svaka promenljiva klasnog tipa, promenljiva a moe sadrati referencu na neki objekat klase int[]. A kao to smo upravo objasnili, objekti klase int[] su nizovi baznog tipa int. Kao i svaki objekat, objekat niza tipa int[] se konstruie operatorom new, dodue u posebnom obliku, a referenca na taj novi niz se zatim moe dodeliti promenljivoj a. Na primer:
a = new int[100];

U ovoj naredbi dodele, vrednost 100 u uglastim zagradama iza re ci int odreduje broj elemenata niza koji se konstruie. Prethodna dva koraka se, kao to je to uobi cajeno, mogu skratiti u jedan:
int[] a = new int[100];

Ovom naredbom deklaracije se alocira promenljiva a klasnog tipa int[], konstruie se objekat niza od 100 elemenata koji su svi primitivnog tipa int i referenca na novokonstruisani objekat niza se dodeljuje promenljivoj a. Svaki od 100 elemenata konstruisanog niza je obi cna promenljiva tipa int c iji sadraj moe biti bilo koja celobrojnu vrednost tipa int. Efekat prethodne deklaracije je ilustrovan na slici 3.2.
a

a[0]

a[1]

a[2]

a[99]

Slika 3.2: Niz a od 100 elemenata celobrojnog tipa. Pomenuli smo da se c esto za promenljivu koja ukazuje na neki niz kra ce govori kao da je sm taj niz. Tako u poslednjem primeru kra ce kaemo niz a od 100 elemenata. Medutim, promenljiva a je obi cna promenljiva klasnog tipa i njena vrednost moe biti samo referenca na neki objekat niza, ili referenca null. Deklaracija niza c iji je bazni tip druga ciji primitivni tip od int nije mnogo razli cita re c int treba zamenti imenom drugog primitivnog tipa i u uglastim zagradama navesti potrebnu duinu niza. Neto sli cno vai i za nizove c iji je bazni tip neki klasni tip. Na primer:
Student[] s = new Student[15];

Ovom deklaracijom se alocira promenljiva s klasnog tipa Student[], konstruie se niz od 15 elemenata klasnog tipa Student i referenca na novi niz se dodeljuje promenljivoj s. Svaki od 15 elemenata ovog niza predstavlja pro-

80

3. N IZOVI

menljivu klasnog tipa Student c iji sadraj moe biti referenca na objekte klase Student ili null. Jedan primer sadraja niza s prikazan je na slici 3.3.
s

null

s[0]

s[1]

s[2]

s[14]

Student

Student

Slika 3.3: Niz s sa elementima klasnog tipa Student. U optem slu caju, ako je N celobrojni izraz, onda se operatorom new u izrazu
new bazni-tip [N]

konstruie niz koji kao objekat pripada klasi bazni-tip[] i vra ca se referenca na taj niz. Izra cunata vrednost n celobrojnog izraza N u uglastim zagradama odreduje duinu tog niza, odnosno broj njegovih elemenata. Nakon konstruisanja nekog niza, broj elementa tog niza se ne moe promeniti. Prema tome, iako se moe pisati, recimo,
int k = 5; double[] x = new double[2*k+10];

to ne zna ci da je broj elemenata realnog niza x promenljiv, ve c je ksiran u trenutku njegovog konstruisanja vredno cu celobrojnog izraza 2*k+10 u uglastim zagradama koja iznosi 20. U ovom primeru dakle, niz x ima ta cno 20 elemenata i taj njegov broj elemenata se kasnije ne moe niti smanjiti niti pove cati. Svaki niz u Javi, pored svojih elemenata, automatski sadri i jedno javno celobrojno polje length u kojem se nalazi duina niza. Zato, u prethodnom primeru, u programu se moe koristiti polje x.length c ija je vrednost 20 nakon konstruisanja niza x. Vrednost tog polja se naravno ne moe menjati u programu. To je obezbedeno time to je polje length svakog niza deklarisano s modikatorom final, pa se ne moe menjati nakon inicijalizacije. Poto elementi niza u sutini predstavljaju polja objekta niza, ti elementi se u trenutku konstruisanja niza inicijalizuju podrazumevanim vrednostima za bazni tip niza. (Podsetimo se jo jednom: podrazumevane vrednosti su

3.1. Jednodimenzionalni nizovi

81

nula za numeri cki tip, false za logi cki tip, \u0000za znakovni tip i null za klasni tip.) Po cetne vrednosti elemenata niza se mogu i eksplicitno navesti u deklaraciji niza, unutar viti castih zagrada i medusobno razdvojene zapetama. Tako, naredbom
int[] a = new int[] {1, 2, 4, 8, 16, 32, 64, 128};

konstruie se niz a od 8 elementa c ije su po cetne vrednosti 1, 2, 4, 8, 16, 32, 64, 128. Drugim re cima, element a[0] dobija po cetnu vrednost 1, element a[1] dobija po cetnu vrednost 2 i tako dalje, element a[7] dobija po cetnu vrednost 128. Opti oblik operatora new u ovom kontekstu je:
new bazni-tip [] { lista-vrednosti }

U stvari, po cetni deo new bazni-tip [] nije obavezan i c esto se izostavlja u naredbi deklaracije niza s po cetnim vrednostima. Rezultat prethodnog izraza je referenca na novokonstruisani niz sa elementima koji su inicijalizovani datim vrednostima. Zbog toga se takav izraz moe koristiti u programu na svim mestima gde se o cekuje niz tipa bazni-tip []. Na primer, ako je prikai() metod c iji je jedini parametar tipa niza stringova, onda se u programu taj metod moe kratko pozvati u obliku:
prikai(new String[] {"Nastavi", "Odustani"});

Ovaj primer pokazuje da je konstruisanje anonimnih nizova ponekad vrlo korisno. Bez te mogu cnosti bi za neke pomo cne nizove u programu morale da se piu naredbe deklaracije (i smiljaju imena) za promenljive koje ukazuju na ne toliko vane nizove. Na primer:
String[] opcije = new String[] {"Nastavi", "Odustani"}; prikai(opcije);

Ukoliko se elementima niza eksplicitno dodeljuju po cetne vrednosti prilikom konstruisanja, duina niza se nigde ne navodi i implicitno se zaklju cuje na osnovu broja navedenih vrednosti. Pored toga, te vrednosti ne moraju da budu obi cne konstante, nego mogu biti promenljive ili proizvoljni izrazi pod uslovom da su njihove vrednosti odgovaraju ceg baznog tipa niza. Na primer:
Student pera = new Student("Pera Peri c", 1111, 99, 100, 100); Student[] odlikai = new Student[] { pera, new Student("Laza Lazi c", 2222, 99, 95, 100), new Student("Mira Miri c", 3333, 100, 100, 100) };

82

3. N IZOVI

Kori cenje nizova


Elementi niza su obi cne promenljive baznog tipa niza i mogu se koristiti u programu na svim mestima gde je dozvoljena upotreba promenljive baznog tipa niza. Ovi elementi niza se koriste preko svojih indeksa i imena promenljive koja ukazuje na objekat niza. Na primer, jedan niz a c ija je deklaracija
int[] a = new int[1000];

u programu zapravo obezbeduje nita drugo do 1000 celobrojnih promenlji vih sa imenima a[0], a[1], . . . , a[999]. Puna snaga nizova ipak ne lei samo u mogu cnosti lakog dobijanja velikog broja promenljivih za c uvanje velikog broja podataka. Druga odlika nizova koja ih izdvaja medu ca rada sa njihovim pro strukturama podataka jeste lako izvoljnim elementima. Ova druga mogu cnost u radu sa nizovima je posledica c injenice da se za indekse elemenata nizova mogu koristiti proizvoljni celobrojni izrazi. Tako, ako je i promenljiva tipa int, onda su, recimo, a[i] i a[3*i-7] takode ispravni zapisi elemenata prethodnog niza a. Elementi niza a na koje se odnose ovi zapisi se odreduju dinami cki tokom izvravanja pro grama. Naime, zavisno od konkretne vrednosti promenljive i, element niza na koji se odnosi zapis, recimo, a[3*i-7] dobija se izra cunavanjem vrednosti celobrojnog izraza u uglastim zagradama: ako promenljiva i ima vrednost 3, dobija se element a[2]; ako promenljiva i ima vrednost 10, dobija se element a[23] i sli cno. Prakti cniji primer je slede ca jednostavna petlja kojom se na ekranu prikazuju vrednosti svih elemenata niza a:
for (int i = 0; i < a.length; i++) System.out.println(a[i]);

Telo ove petlje se sastoji samo od jedne naredbe kojom se prikazuje i -ti element niza a. Po cetna vrednost broja ca i je 0, pa se u prvoj iteraciji prikazuje element a[0]. Zatim se izrazom i++ broja c i uve cava za 1 i dobija njegova nova vrednost 1, pa se u drugoj iteraciji prikazuje element a[1]. Ponavljaju ci ovaj postupak, broja c i se na kraju svake iteracije uve cava za 1 i time se redom prikazuju vrednosti elemenata niza a. Poslednja iteracija koja se izvrava je ona kada je na po cetku vrednost broja ca i jednaka 999 i tada se prikazuje element a[999]. Nakon toga c e i uve canjem za 1 dobiti vrednost 1000 i zato uslov nastavka petlje i < a.length ne ce biti zadovoljen poto polje a.length ima vrednost 1000. Time se prekida izvravanje petlje i zavrava prikazivanje vrednosti ta cno svih elemenata niza a. Obratite panju na jednostavnost gornje petlje kojom je realizovan zadatak prikazivanja svih elemenata niza a pomo cu prakti cno dva reda se

3.1. Jednodimenzionalni nizovi

83

prikazuju vrednosti 1000 promenljivih! Ne samo to, i da niz a ima 100000 ili vie elemenata, potpuno istom petljom bi se prikazale vrednosti mnogo ve ceg broja tih elemenata. U radu sa nizovima u Javi su mogu ce dve vrste greaka koje izazivaju prekid izvravanja programa. Prvo, ako promenljiva a tipa niza sadri vrednost null, onda promenljiva a c ak ni ne ukazuje na neki niz, pa naravno nema smisla koristiti neki element a[i] nepostoje ceg niza. Drugo, ako promenljiva a zaista ukazuje na prethodno konstruisan niz, onda vrednost indeksa i za element a[i] moe pogreno biti van dozvoljenih granica indeksa niza. To c e biti slu caj kada se izra cunavanjem indeksa i dobije da je i < 0 ili i >= a.length.

Primer: prebrojavanje glasova na izborima


Pretpostavimo da na jednom bira ckom mestu treba prebrojati glasove sa glasa ckih listi ca posle zavretka glasanja. Naravno, umesto ru cnog brojanja koje je podlono grekama, potrebno je napisati program u Javi kojim se glasovi unose i prebrojavaju kako se redom pregledaju glasa cki listi ci. Budu ci da se program pie mnogo pre raspisivanja izbora radi njegovog potpunog testiranja, unapred se ne zna broj partija koji izlazi na izbore, pa taj podatak treba da bude deo ulaza programa. U programu se koristi celobrojni niz c iji elementi sadre broj glasova pojedinih partija. Drugim re cima, ako su partije u programu numerisane od 0, onda se za sabiranje njihovih glasova koristi niz c iji i -ti element sadri broj glasova i -te partije. Prestanak unoenja glasova se programski realizuje unosom glasa za nepostoje cu partiju. Nakon toga se u programu prikazuje ukupan broj glasova svih partija.
import java.util.*; public class Glasanje { public static void main(String[] args) { Scanner tastatura = new Scanner(System.in); // U citavanje ukupnog broja partija System.out.print("Unesite ukupan broj partija: "); int brojPartija = tastatura.nextInt(); // Konstruisanje niza za sabiranje glasova partija int[] partije = new int[brojPartija]; // U citavanje i brojanje glasova za pojedina cne partije

84

3. N IZOVI

for ( ; ; ) { // beskona cna petlja System.out.print("Redni broj partije koja dobija glas> "); int p = tastatura.nextInt(); if (p < 1 || p > brojPartija) break; else partije[p-1] = partije[p-1] + 1; } // Prikazivanje osvojenih glasova svih partija for (int i = 0; i < partije.length; i++) { System.out.print("Partija pod rednim brojem " + (i+1)); System.out.println(" ima " + partije[i] + " glasova."); } } }

Primetimo da su indeksi niza partije u programu morali da budu prilagodeni za 1, poto su partije na glasa ckim listi cima numerisane od 1 a nizovi u Javi od 0.

Primer: kopiranje nizova


Ako su a i b promenljive bilo kog, istog tipa, onda se naredbom dodele a = b sadraj promenljive b prepisuje u promenljivu a. Ovaj efekat u sluc aju promenljivih nizova a i b ponekad nije odgovaraju ci, nego je potrebno napraviti jo jednu identi cnu kopije svih elemenata niza b na koje ukazuju promenljive a. Konkretnije, ako je konstruisan niz b naredbom, recimo,
double[] b = new double[20];

i ako je deklarisana promenljiva a tipa istog tog niza, recimo,


double[] a;

onda se naredbom dodele


a = b;

u promenljivu a prepisuje samo referenca iz promenljive b. To zna ci da na konstruisani objekat niza na koji je prvobitno ukazivala promenljiva b, sada ukazuju obe promenljive a i b, ali da i dalje postoji samo po jedan primerak svakog elementa niza. Zbog toga se ti elementi sada mogu koristiti na dva na cina: zapisi a[i] i b[i] se odnose na jedinstveni i -ti element niza. Ukoliko je potrebno zi cki kopirati svaki element niza, to se mora uraditi ru cno. (Ili upotrebom posebnog metoda standardne klase Arrays.) Radi ilustracije, u nastavku je prikazan poseban metod kojim se ovaj problem reava za nizove tipa double.

3.1. Jednodimenzionalni nizovi

85

Bilo koji tip niza u Javi je tip kao i svaki drugi, pa se moe koristiti na sve na cine na koje se mogu koristiti tipovi u Javi. Speci cno, tip niza moe biti tip nekog parametra metoda, kao i tip rezultata metoda. Upravo se ova c injenica koristi za metod kojim se zi cki kopiraju svi elementi jednog niza u drugi:
public static double[] kopirajNiz(double[] original) { if (original == null) return null; double[] kopija = new double[original.length]; for (int i = 0; i < kopija.length; i++) kopija[i] = original[i]; return kopija; }

Ako su a i b promenljive koje su denisane kao na po cetku ovog primera, onda se naredbom dodele
a = kopirajNiz(b);

dobija novi niz na koga ukazuje a. Elementi novog niza su zi cke kopije elementa niza na koji ukazuje b tako da se sada zapisi a[i] i b[i] odnose na razli cite i -te elemente odgovaraju cih nizova.

Primer: argumenti programa u komandnom redu


Pokretanje nekog programa za izvravanje od strane Java interpretatora postie se pisanjem njegove glavne klase kao argumenta Java interpretatora. Ta can na cin kako se to izvodi na ra cunaru zavisi od razvojnog okruenja pod c ijom kontrolom se pie Java program, ali na kraju se sve svodi na jednu komandu operativnog sistema u obliku:
java glavna-klasa

Ovom komandom se pokre ce Java interpretator java koji sa svoje strane poc inje izvravanje programa pozivom specijalnog metoda main() koji se nalazi u navedenoj glavnoj klasi. Metod main() se dakle ne poziva direktno u nekom drugom metodu programa, nego ga indirektno poziva Java interpretator na samom po cetku izvravanja celog programa. Zaglavlje metoda main() je skoro ksirano:
public static void main(String[] args)

Metod main() mora imati slubeno ime kako bi se u Java interpretatoru pozvao upravo taj metod radi izvravanja programa. Taj metod mora biti denisan sa modikatorom public, jer ina ce metod ne bi bio slobodno dostupan i ne bi uopte mogao da se pozove u Java interpretatoru. Taj metod mora biti denisan i sa modikatorom static, jer bi ina ce metod mogao da se pozove

86

3. N IZOVI

samo uz neki objekat glavne klase, a takav objekat ne moe biti konstruisan pre po cetka izvravanja programa. Pored toga, iz zaglavlja se moe uo citi da metod main() ima jedan parametar args tipa String[].1 To zna ci da se metodu main() prilikom poziva moe kao argument preneti niz stringova. Ali budu ci da se metod main() poziva implicitno, kako se kao njegov argument moe navesti neki niz stringova koje treba preneti glavnom metodu? Ovo se postie pisanjem eljenog niza stringova u komandnom redu iza glavne klase c iji se metod main() poziva:
java glavna-klasa niz-stringova

Metod main() dobija dakle svoj argument iz komandnog reda tako to Java interpretator automatski inicijalizuje niz args stringovima koji su eventualno navedeni iza glavne klase u komandnom redu. Kao prakti can primer, u nastavku je prikazan program kojim se samo prikazuje niz stringova naveden kao argument programa u komandnom redu.
public class Poruka { public static void main(String[] args) { // Da li su navedeni argumenti u komandnom redu? if (args.length == 0) return; // Ispitivanje prvog argumenta u komandnom redu if (args[0].equals("-d")) System.out.print("Dobar dan"); else if (args[0].equals("-z")) System.out.print("Zbogom"); else return; // Prikazivanje ostalih argumenata u komandnom redu for (int i = 1; i < args.length; i++) System.out.print(" " + args[i]); System.out.println("!"); } }

Ako se ovaj program izvri komandom


java Poruka -z okrutni svete

onda elementi niza args u metodu main() dobijaju slede ce vrednosti:


1 Ime parametra args metoda main() je tradicionalno, ali proizvoljno, i jedino se ono moe promeniti u zaglavlju metoda main().

3.1. Jednodimenzionalni nizovi

87

args[0] = "-z"; args[1] = "okrutni"; args[2] = "svete";

Zbog toga, izvravanjem prethodnog programa se na ekranu prikazuje ova poruka:


Zbogom okrutni svete!

Ako se pak program izvri komandom


java Poruka -d tugo

onda elementi niza args u metodu main() dobijaju slede ce vrednosti:


args[0] = "-d"; args[1] = "tugo";

U ovom slu caju se izvravanjem prethodnog programa na ekranu prikazuje druga poruka:
Dobar dan tugo!

Klasa Arrays
Radi lakeg rada sa nizovima, u Javi se moe koristiti standardna klasa Arrays iz paketa java.util. Ova pomo cna klasa sadri nekoliko stati ckih metoda koji obezbeduju korisne operacije nad nizovima c iji je bazni tip jedan od primitivnih tipova. U nastavku je naveden nepotpun spisak i opis ovih metoda, pri c emu oznaka tip ukazuje na jedan od primitivnih tipova. Izuzetak su jedino metodi sort() i binarnySearch() kod kojih nije dozvoljeno da to bude boolean. String toString(tip[] a) vra ca reprezentaciju niza a u obliku stringa. U tom obliku, vrednosti svih elemenata niza a se nalaze unutar uglastih zagrada po redu njihovih pozicija u nizu i medusobno su raz dvojene zapetama. tip[] copyOf(tip[] a, int n) vra ca novu kopiju niza a koja se sastoji od prvih n njegovih elemenata. Ako je je vrednost n ve ca od vrednosti a.length, onda se viak elemenata kopije niza inicijalizuje nulom ili vredno cu false. U suprotnom slu caju, kopira se samo po cetnih n elemenata niza a. ca novu kopiju niza tip[] copyOfRange(tip[] a, int i, int j) vra a od indeksa i do indeksa j. Element sa indeksom i niza a se uklju cuje, dok se onaj sa indeksom j ne uklju cuje u novi niz. Ako je indeks j ve ci od a.length, viak elemenata kopije niza se inicijalizuje nulom ili vredno cu false.

88

3. N IZOVI

void sort(tip[] a) sortira niz a u mestu u rastu cem redosledu. int binarnySearch(tip[] a, tip v) nalazi datu vrednost v u sortiranom nizu a koriste ci binarnu pretragu. Ako je vrednost v nadena u nizu, vra ca se indeks odgovaraju ceg elementa. U suprotnom slu caju, vra ca se negativna vrednost k tako da k 1 odgovara poziciji gde bi data vrednost trebalo da se nalazi u sortiranom nizu. void fill(tip[] a, tip v) dodeljuje svim elementima niza a vrednost v. ca vrednost true ukoliko ni boolean equals(tip[] a, tip[] b) vra zovi a i b imaju istu duinu i jednake odgovaraju ce elemente. U suprotnom slu caju, vra ca vrednost false.

Primer: izvla cenje loto brojeva


Radi ilustracije primene klase Arrays u radu sa nizovima, u nastavku je prikazan program koji simulira izvla cenje brojeva u igri na sre cu loto. U igri loto se izvla ci 7 slu cajnih, medusobno razli citih brojeva medu celim brojevima od 1 do 39. U programu se generiu 7 takvih brojeva koji predstavljaju jedno kolo igre loto. (Drugi ugao gledanja na rezultat programa je da generisane brojeve igra c moe igrati u stvarnoj igri da bi osvojio glavnu nagradu.) U programu se koristi konstanta N za ozna cavanje duine niza svih moguc ih brojeva, kao i konstanta K za ozna cavanje duine niza brojeva jednog kola izvla cenja. Naravno, njihove vrednosti 39 i 7 mogle bi se i direktno koristiti u programu, ali onda bi se program tee mogao prilagoditi za neka druga pravila igre loto. (Na primer, negde se izvla ci 5 brojeva od 36.) ce brojeve od 1 do N koji u cestvuju Celobrojni niz brojevi sadri sve mogu u jednom kolu igre loto. Ovaj niz je podeljen u dva dela: u levom delu su brojevi koji su preostali za izvla cenje, a u desnom delu su oni brojevi koji su ve c izvu ceni. Granica izmedu vredno cu promenljive dva dela niza je odredena m koja sadri indeks poslednjeg elementa levog dela. Na po cetku pre prvog izvla cenja, vrednost promenljive m je jednaka indeksu poslednjeg elementa niza brojevi. Za izvla cenje slu cajnih brojeva se koristi metod Math.random(), ali problem je to to se time ne moraju obavezno dobiti razli citi izvu ceni brojevi lotoa. Zbog toga se u stvari generie slu cajni indeks levog dela niza brojevi i uzima broj u elementu niza koji se nalazi na toj poziciji. Zatim se taj element medusobno zamenjuje sa poslednjim elementom levog dela niza brojevi i pomera ulevo granica izmedu dva dela niza smanjivanjem vrednosti m za je-

3.1. Jednodimenzionalni nizovi

89

dan. Ponavljanjem ovog postupka K puta za svaki izvu ceni broj, na kraju se u desnom delu niza brojevi nalaze svi izvu ceni slu cajni brojevi. Da bi se ovi izvu ceni brojevi prikazali u rastu cem redosledu, taj desni deo niza brojevi, od indeksa N - K do kraja, kopira se u novi niz kombinacija i ovaj niz se sortira odgovaraju cim metodom klase Arrays.
import java.util.*; public class Loto { public static void main (String[] args) { final int N = 39; final int K = 7; // duina niza svih brojeva // duina izvu cene kombinacije

// Inicijalizacija niza brojevima 1, 2, ..., n int[] brojevi = new int[N]; for (int i = 0; i < N; i++) brojevi[i] = i + 1; int m; // granica levog i desnog dela niza

// Izvla cenje K brojeva i premetanje u desni deo niza for (m = N-1; m > N-K-1; m--) { // Generisanje slu cajnog indeksa levog dela niza int i = (int) (Math.random() * (m+1)); // Me dusobna zamena slu cajnog elementa i poslednjeg // elementa levog dela niza int broj = brojevi[i]; brojevi[i] = brojevi[m]; brojevi[m] = broj; } // Kopiranje izvu cenih brojeva u novi niz int[] kombinacija = Arrays.copyOfRange(brojevi,m+1,N); // Sortiranje novog niza Arrays.sort(kombinacija); // Prikazivanje izvu cenih brojeva u rastu cem redosledu System.out.println("Dobitna kombinacija je: "); for (int i = 0; i < K; i++) System.out.print(kombinacija[i] + " "); System.out.println(); } }

90

3. N IZOVI

Naredba for-each
U verziji Java 5.0 je dodat novi oblik for petlje koji omogu cuje laki rad sa svim elementima nekog niza. Ova petlja se popularno naziva for-each petlja, iako se re c each ne pojavljuje u njenom zapisu. U stvari, for-each petlja se moe koristiti ne samo za nizove, nego i za optije strukture podataka o kojima se govori u poglavlju 11. Ako je niz tipa bazni-tip [], onda for-each petlja za niz ima opti oblik:
for (bazni-tip elem : niz) { . . // Obrada aktuelnog elementa elem . }

Obratite panju na to da je znak dve ta cke deo sintakse ove petlje. Pored toga, elem je kontrolna promenljiva baznog tipa koja se mora deklarisati unutar petlje. Prilikom izvravanja for-each petlje, kontrolnoj promenljivoj elem se redom dodeljuje vrednost svakog elementa niza i izvrava se telo petlje za svaku tu vrednost. Preciznije, prethodni oblik for-each petlje je ekvivalentan slede coj obi cnoj for petlji:
for (int i = 0; i < niz.length; i++) { bazni-tip elem = niz[i]; . . // Obrada aktuelnog elementa elem . }

Na primer, sabiranje svih pozitivnih elemenata niza a tipa double[] moe se uraditi for-each petljom na slede ci na cin:
double zbir = 0; for (double e : a) { if (e > 0) zbir = zbir + e; }

Ili, prikazivanje izvu cenih loto brojeva u programu na prethodnoj strani moe se uraditi na elegantniji na cin:
for (int broj : kombinacija) System.out.print(broj + " ");

Primetimo da je for-each petlja korisna u slu cajevima kada je potrebno uraditi neto sa svim elementima nekog niza, jer se ne mora voditi ra cuna o indeksima i granici tog niza. Medutim, ona nije od velike pomo ci ako treba neto uraditi samo sa nekim elementima niza, a ne sa svim elementima.

3.1. Jednodimenzionalni nizovi

91

Obratite panju i na to da se u telu for-each petlje zapravo samo c itaju vrednosti elemenata niza (i dodeljuju kontrolnoj promenljivoj), dok upisivanje vrednosti u elemente niza nije mogu ce. Na primer, ako treba svim elementima prethodno konstruisanog celobrojnog niza a dodeliti neku vrednost, recimo 17, onda bi bilo pogreno napisati:
for (int e : a) { e = 17; }

U ovom slu caju se kontrolnoj promenljivoj e redom dodeljuju vrednosti elemenata niza a, pa se odmah zatim promenljivoj e dodeljuje vrednost 17. Medutim, to nema nikakav efekat na elemente niza i njihove vrednosti ostaju nepromenjene.

Metodi sa promenljivim brojem argumenata


Od verzije Java 5.0 je omogu ceno pozivanje metoda sa promenljivim brojem argumenata. Metod System.out.printf() za formatizovano prikazivanje vrednosti na ekranu predstavlja primer metoda sa promenljivim brojem argumenata. Naime, prvi argument metoda System.out.printf() mora biti tipa String, ali ovaj metod moe imati proizvoljan broj dodatnih argumenata bilo kog tipa. Poziv metoda sa promenljivim brojem argumenata se ne razlikuje od poziva drugih metoda, dok njihova denicija zahteva malo druga ciji na cin pisanja. Ovo se najbolje moe razumeti na jednom primeru, pa pretpostavimo da treba napisati metod prosek() kojim se izra cunava i vra ca prosek bilo kog broja vrednosti tipa double. Prema tome, pozivi ovog metoda mogu imati promenljiv broj argumenata, na primer: prosek(1, 2, 3, 4) prosek(1.41, Math.PI, 2.3) prosek(Math.sqrt(3)) prosek() Ovde su dakle u prvom pozivu navedena c etiri argumenta, u drugom pozivu tri argumenta, u tre cem pozivu jedan argument, a u poslednjem pozivu nula argumenata. Zaglavlje denicije metoda prosek() mora pretrpeti male izmene u listi parametara u zagradi u odnosu na uobi cajeni zapis, na primer:
public static double prosek(double... brojevi) { .

92

3. N IZOVI

. // Telo metoda . }

Tri ta cke iza tipa double parametra brojevi u zagradi ukazuju da se na mesto tog parametra moe navesti promenljiv broj argumenata u pozivu metoda. Prilikom poziva metoda prosek(), pridruivanje vie argumenata parametru brojevi se razreava tako to se implicitno najpre konstruie niz brojevi tipa double[] c ija je duina jednaka broju navedenih argumenata, a zatim se elementi tog niza inicijalizuju ovim argumentima. To zna ci da se telo metoda prosek() mora pisati pod pretpostavkom da je na raspolaganju konstruisan niz brojevi tipa double[], da se aktuelni broj njegovih elemenata nalazi u polju brojevi.length i da se vrednosti stvarnih argumenata nalaze u elementima brojevi[0], brojevi[1] i tako dalje. Imaju ci ovo u vidu, kompletna denicija metoda prosek() je:
public static double prosek (double... brojevi) { double zbir = 0; for (int i = 0; i < brojevi.length; i++) zbir = zbir + brojevi[i]; return zbir / brojevi.length; }

Jo bolje, ukoliko se koristi for-each petlja, dobija se elegantnije reenje:


public static double prosek (double... brojevi) { double zbir = 0; for (double broj : brojevi) zbir = zbir + broj; return zbir / brojevi.length; }

Sli can postupak pridruivanja promenljivog broja argumenata nekom parametru metoda se primenjuju i u optem slu caju. Naime, ako je taj parametar tipa T, u trenutku poziva se konstruie odgovaraju ci niz tipa T[] i njegovi elementi se inicijalizuju datim argumentima. Primetimo da parametar kome odgovara promenljiv broj argumenata (tj. onaj iza c ijeg tipa se nalaze tri ta cke) mora biti poslednji parametar u zaglavlju denicije metoda. Razlog za ovo je prosto to to se u pozivu metoda podrazumeva da njemu odgovaraju svi navedeni argumenti do kraja, odnosno do zatvorene zagrade. Obratite panju i na to da se u pozivu, na mesto parametra kome odgovara promenljiv broj argumenata, moe navesti stvarni niz, a ne lista pojedina cnih vrednosti. Tako, u prethodnom primeru, ako je u programu prethodno konstruisan niz ocene tipa double[], onda je ispravan poziv prosek(ocene) radi dobijanja proseka vrednosti ocena u nizu.

3.2. Dvodimenzionalni nizovi

93

3.2 Dvodimenzionalni nizovi


Nizovi o kojima smo govorili do sada poseduju samo jednu dimenziju duinu. Obi cni nizovi se zato c esto nazivaju jednodimenzionalni nizovi. U Javi se mogu koristiti i nizovi koji imaju dve dimenzija irinu i visinu, tri dimenzija irinu, visinu i dubinu, i tako dalje. Zato se o ovim nizovima govori kao dvodimenzionalnim, trodimenzionalnim ili, jednom re cju, viedimenzionalnim. U Javi se svaki tip podataka moe koristiti za bazni tip nekog (jednodimenzionalnog) niza. Speci cno, kako je neki tip niza takode can tip u obi Javi, ukoliko je bazni tip nekog niza ba takav tip, dobija se niz nizova. Na primer, jedan celobrojni niz ima tip int[], a to zna ci da automatski postoji i tip int[][] koji predstavlja niz celobrojnih nizova. Ovaj niz nizova se kra ce zove dvodimenzionalni niz. Nita nije neobi cno da se dalje posmatra i tip int[][][] koji predstavlja niz dvodimenzionalnih nizova, odnosno trodimenzionalni niz. Naravno, ova linija razmiljanja se moe nastaviti i tako se dobijaju viedimenzionalni nizovi, mada se nizovi c ija je dimenzija ve ca od tri zaista retko primenjuju. U daljem tekstu se ograni cavamo samo na dvodimenzionalne nizove, jer se svi koncepti u vezi s njima lako proiruju na vie dimenzija. Dvodimenzionalni nizovi se popularno nazivaju i matrice ili tabele. Deklarisanje promenljive tipa dvodimenzionalnog niza je sli cno deklarisanju obi cnog, jednodimenzionalnog niza razlika je samo u dodatnom paru uglastih zagrada. Naredbom, na primer,
int[][] a;

deklarie se promenljiva a c iji sadraj moe biti referenca na objekat dvodimenzionalnog niza tipa int[][]. Konstruisanje aktuelnog dvidimenzionalnog niza se vri operatorom new kao i u jednodimenzionalnom slu caju. Na primer, naredbom dodele
a = new int[3][4];

konstruie se dvodimenzionalni niz veli cine 3 4 i referenca na njega se dodeljuje prethodno deklarisanoj promenljivoj a. Kao i obi cno, ove dve posebne naredbe se mogu spojiti u jednu:
int[][] a = new int[3][4];

Na slici 3.4 je prikazan izgled relevantne memorije ra cunara nakon izvravanja ove naredbe dodele. Ta slika pokazuje da je jedan dvodimenzionalni niz na koji ukazuje promenljiva a najbolje zamisliti kao matricu (ili tabelu)

94

3. N IZOVI

elemenata koja ima tri reda i c etiri kolone. Elementi matrice predstavljaju obi cne, u ovom slu caju celobrojne promenljive koje imaju dvostruke indekse a[i][j]: prvi indeks pokazuje red i drugi indeks kolonu u kojima se element nalazi u matrici. Pri tome treba imati u vidu da, kao to je to uobi cajeno u Javi, numeracija redova i kolona matrice ide od nule, a ne od jedan.
a

a[0][0] a[1][0] a[2][0]

a[0][1] a[1][1] a[2][1]

a[0][2] a[1][2] a[2][2]

a[0][3] a[1][3] a[2][3]

Slika 3.4: Dvodimenzionalni celobrojni niz a veli cine 34. Ipak ta cnije, dvodimenzionalni niz a u prethodnom primeru nije matrica, nego je zaista niz celobrojnih nizova. Tako, izrazom new int[3][4] se zapravo konstruie niz od tri celobrojna niza, od kojih svaki ima c etiri elementa. Pravi izgled dvodimenzionalnog niza a je zato onaj koji je prikazan na slici 3.5.
a
a[0][0] a[0] a[1][0] a[1] a[2] a[2][0] a[2][1] a[2][2] a[2][3] a[1][1] a[1][2] a[1][3] a[0][1] a[0][2] a[0][3]

Slika 3.5: Prava slika dvodimenzionalnog niza a dimenzije 34. Prava slika dvodimenzionalnog niza je komplikovanija za razumevanje i, sre com, moe se u ve cini slu cajeva zanemariti i dvodimenzionalni niz smatrati matricom elemenata. Ponekad je ipak potrebno znati da svaki red matrice predstavlja zapravo jedan niz za sebe. Ovi nizovi u prethodnom primeru su tipa int[] i na njih ukazuju promenljive c ija su imena a[0], a[1] i a[2]. Te promenljive i nizovi se u programu mogu koristiti na svim mestima gde su dozvoljeni obi cni celobrojni nizovi. Na primer, neki red matrice moe biti argument u pozivu metoda c iji je parametar tipa int[]. Zbog prave slike dvodimenzionalnog niza treba imati na umu da vrednost polja a.length u prethodnom primeru iznosi tri, odnosno jednaka je broju redova matrice a. Ako je potrebno dobiti broj kolona matrice, onda je to duina

3.2. Dvodimenzionalni nizovi

95

jednodimenzionalnih nizova od kojih se sastoji svaki red matrice, odnosno a[0].length, a[1].length ili a[2].length. U stvari, svi redovi matrice ne moraju biti jednake duine i u nekim sloenijim primenama se koriste matrice sa razli citim duinama redova. Elementi dvodimenzionalnih nizova se prilikom konstruisanja inicijalizuju odgovaraju cim podrazumevanim vrednostima. To se moe promeniti pisanjem svih po cetnih vrednosti iza operatora new, sli cno na cinu na koji se to postie kod jednodimenzionalnih nizova. Pri tome treba voditi ra cuna da se po cetne vrednosti matrice navode po redovima, odnosno redom za jednodimenzionalne nizove redova matrice u viti castim zagradama. Na primer:
int[][] a = new int[][] {{ 1, -1, 0, 0}, {10, 17, -2, -3}, { 0, 1, 2, 3} };

Kori cenje dvodimenzionalnih nizova u programu se, sli cno jednodimenzionalnom slu caju, zasniva na dvostrukim indeksima pojedinih elemenata matrice. Ovi indeksi mogu biti bilo koji celobrojni izrazi i njihovim izra cunavanjem se dobijaju brojevi reda i kolone u kojima se nalazi odgovaraju ci element matrice. U radu sa dvodimenzionalnim nizovima se c esto koriste ugnjedene for petlje tako da se u spoljanjoj petlji prate redovi matrice, a u unutranjoj petlji se prate kolone matrice. Na taj na cin se za svaki aktuelni red matrice obraduju svi elementi tog reda, a kako se ovo ponavlja za sve redove matrice, time se obraduju ba svi elementi matrice red po red. Na primer, ukoliko za matricu
double[][] a = new double[10][10];

treba elementima te matrice na glavnoj dijagonali dodeliti vrednost 1 i ostalim elementima vrednost 0, to se moe uraditi na slede ci na cin:
for (int i = 0; i < a.length; i++) { // za svaki red // u matrici for (int j = 0; j < a[i].length; j++) { // i za svaku kolonu // u aktuelnom redu if (i == j) // da li je element na glavnoj dijagonali? a[i][j] = 1; else a[i][j] = 0; }

Na sli can na cin se mogu sabrati svi elementi matrice a:


double zbir = 0; for (int i = 0; i < a.length; i++)

96

3. N IZOVI

for (int j = 0; j < a[i].length; j++) zbir = zbir + a[i][j];

Sabiranje svih elemenata matrice a moe se posti ci i ugnjedenim for-each petljama:


double zbir = 0; for (double[] red : a) for (double e : red) zbir = zbir + e;

Primer: rad sa tabelarnim podacima


Dvodimenzionalni nizovi su naro cito korisni za c uvanje podataka koji se prirodno predstavljaju u obliku tabele po redovima i kolonama. Radi konkretnosti, razmotrimo primer rme ABC sa 10 prodavnica koja vodi podatke o mese cnom protu svake prodavnice tokom neke godine. Tako, ako su prodavnice numerisane od 0 do 9 i meseci jedne godine od 0 do 11, onda se ovi podaci o protu mogu predstaviti matricom profit na slede ci na cin:
double[][] profit = new double[10][12];

Na ovaj na cin su redovima matrice profit obuhva cene prodavnice rme, dok kolone te matrice ukazuju na mesece godine. Element matrice, recimo, profit[5][2] ozna cava vrednost prota koji je ostvarila prodavnica broj 5 u mesecu martu. Optije, element matrice profit[i][j] sadri vrednost prota koji je ostvarila i -ta prodavnica u j -tom mesecu (sa numeracijom prodavnica i meseca od 0). Jednodimenzionalni niz profit[i], koji predstavlja i -ti red dvodimenzionalnog niza profit, sadri dakle vrednosti prota koji je ostvarila i -ta prodavnica tokom cele godine po mesecima. Na osnovu podataka u matrici profit mogu se dobiti razli citi analiti cki pokazatelji o poslovanju rme ABC. Ukupni godinji prot rme, na primer, moe se izra cunati na slede ci na cin:
public double godinjiProfit() { double ukupniProfit = 0; for (int i = 0; i < 10; i++) for (int j = 0; j < 12; j++) ukupniProfit += profit[i][j]; return ukupniProfit; }

Cesto je potrebno obraditi samo pojedine redove ili pojedine kolone matrice podataka. Da bi se, recimo, izra cunao ukupni prot svih prodavnica u

3.2. Dvodimenzionalni nizovi

97

datom mesecu, treba sabrati vrednosti prota u koloni koja odgovara tom mesecu:
private double profitZaMesec(int m) { double mese cniProfit = 0; for (int i = 0; i < 10; i++) mese cniProfit += profit[i][m]; return mese cniProfit; }

Ovaj metod se moe iskoristiti za prikazivanje ukupnog prota svih prodavnica po svim mesecima:
public void prikaiProfitPoMesecima() { if (profit == null) { System.out.println("Greka: podaci nisu uneseni!"); return; } System.out.println("Ukupni profit prodavnica po mesecima:"); for (int m = 0; m < 12; m++) System.out.printf("%6.2f", profitZaMesec(m)); System.out.println(); }

Korisno je imati i pokazatelj ukupnog prota pojedinih prodavnica za celu godinu. To prevedeno za matricu prota zna ci da treba formirati jednodimenzionalni niz c iji elementi predstavljaju zbir vrednosti pojedinih redova matrice. Ovaj postupak i prikazivanje dobijenih vrednosti elemenata jednodimenzionalnog niza, uz prethodnu proveru da li je matrica prota zaista popunjena podacima, obuhva ceni su slede cim metodom:
public void prikaiProfitPoProdavnicama() { if (profit == null) { System.out.println("Greka: podaci nisu uneseni!"); return; } double[] profitProdavnice = new double[n]; for (int i = 0; i < 10; i++) for (int j = 0; j < 12; j++) profitProdavnice[i] += profit[i][j]; System.out.println("Ukupni profit firme po prodavnicama:"); for (int i = 0; i < 10; i++) { System.out.print("Prodavnica " + i + ": "); System.out.printf("%7.2f", profitProdavnice[i]);

98

3. N IZOVI

System.out.println(); } }

U nastavku je prikazan kompletan program kojim se korisniku nude razne opcije za rad sa podacima o protu neke rme. Program se sastoji od dve klase. Klasa Firma opisuje konkretnu rmu i sadri polje n za broj njenih prodavnica, kao i polje profit koje ukazuje na njenu matricu prota. Pored ovih atributa, klasa Firma sadri i prethodne metode kojima se obraduje matrica prota konkretne rme.
import java.util.*; public class Firma { private int n; private double[][] profit; // Konstruktor public Firma(int n) { this.n = n; } // Geter metod za polje profit public double[][] getProfit() { return profit; } public void unesiProfit() { profit = new double[n][12]; Scanner tastatura = new Scanner(System.in); for (int i = 0; i < n; i++) for (int j = 0; j < 12; j++) { System.out.print("Unesite profit prodavnice " + i); System.out.print(" za mesec " + j + ": "); profit[i][j] = tastatura.nextDouble(); } } public void prikaiProfit() { if (profit == null) { System.out.println("Greka: podaci nisu uneseni!"); return; } // broj prodavnica firme // matrica profita firme

3.2. Dvodimenzionalni nizovi

99

System.out.print("Tabela profita "); System.out.println("po prodavnicama i mesecima:"); for (int i = 0; i < n; i++) { for (int j = 0; j < 12; j++) System.out.printf("%6.2f", profit[i][j]); System.out.println(); } } public double godinjiProfit() { double ukupniProfit = 0; for (int i = 0; i < n; i++) for (int j = 0; j < 12; j++) ukupniProfit += profit[i][j]; return ukupniProfit; } private double profitZaMesec(int m) { double mese cniProfit = 0; for (int i = 0; i < n; i++) mese cniProfit += profit[i][m]; return mese cniProfit; } public void prikaiProfitPoMesecima() { if (profit == null) { System.out.println("Greka: podaci nisu uneseni!"); return; } System.out.println("Ukupni profit prodavnica po mesecima:"); for (int m = 0; m < 12; m++) System.out.printf("%6.2f", profitZaMesec(m)); System.out.println(); } public void prikaiProfitPoProdavnicama() { if (profit == null) { System.out.println("Greka: podaci nisu uneseni!"); return; } double[] profitProdavnice = new double[n]; for (int i = 0; i < n; i++) for (int j = 0; j < 12; j++)

100

3. N IZOVI

profitProdavnice[i] += profit[i][j]; System.out.println("Ukupni profit firme po prodavnicama:"); for (int i = 0; i < n; i++) { System.out.print("Prodavnica " + i + ": "); System.out.printf("%7.2f", profitProdavnice[i]); System.out.println(); } } }

Druga klasa slui za izbor pojedinih opcija iz korisni ckog menija radi dobijanja razli citih pokazatelja o protu rme ABC na ekranu.
import java.util.*; public class ProfitFirme { public static void main(String[] args) { System.out.print("Program za rad sa tabelom profita "); System.out.print("koji je ostvarilo 10 prodavnica "); System.out.println("firme za 12 meseci."); System.out.println(); Firma abc = new Firma(10); // firma sa 10 prodavnica Scanner tastatura = new Scanner(System.in); int brojOpcije; do { prikaiMeni(); brojOpcije = tastatura.nextInt(); switch (brojOpcije) { case 1: abc.unesiProfit(); break; case 2: abc.prikaiProfit(); break; case 3: if (abc.getProfit() == null) System.out.println( "Greka: podaci nisu uneseni!"); else { System.out.print( "Ukupni godinji profit firme:"); System.out.printf( "%8.2f", abc.godinjiProfit());

3.3. Dinami cki nizovi

101

System.out.println(); } break; case 4: abc.prikaiProfitPoMesecima(); break; case 5: abc.prikaiProfitPoProdavnicama(); break; case 0: System.out.println("Kraj programa ..."); break; default: System.out.println("Greka: pogrena opcija!"); } } while (brojOpcije != 0); } private static void prikaiMeni() { System.out.println(); System.out.println("Izaberite jednu od ovih opcija:"); System.out.println(" 1. Unos tabele profita"); System.out.println(" 2. Prikaz tabele profita"); System.out.println(" 3. Prikaz ukupnog godinjeg profita"); System.out.println(" 4. Prikaz profita po mesecima"); System.out.println(" 5. Prikaz profita po prodavnicama"); System.out.println(" 0. Kraj rada"); System.out.print("Unesite broj opcije: "); } }

3.3 Dinami cki nizovi


Broj elemenata obi cnog niza je ksiran u trenutku konstruisanja niza i ne moe se vie menjati. Duina niza odreduje dakle maksimalan broj podataka koji se pojedina cno c uvaju u elementima niza, ali svi elementi niza ne moraju biti iskori ceni u svakom trenutku izvravanja programa. Naime, u mnogim primenama broj podataka koji se c uvaju u elementima niza varira tokom izvravanja programa. Jedan primer je program za pisanje dokumenata u kojem se pojedina cni redovi teksta dokumenta c uvaju u nizu tipa String[]. Drugi primer je program za neku kompjutersku igru preko Interneta u kojem se koristi niz c iji elementi sadre igra ce koji trenutno u cestvuju u igri. U ovim i

102

3. N IZOVI

sli cnim primerima je karakteristi cno to to postoji niz (relativno velike) ksne duine, ali je niz popunjen samo delimi cno. Ovaj na cin kori cenja nizova ima nekoliko nedostatka. Manji problem je to to se u programu mora voditi ra cuna o tome koliko je zaista iskori cen niz. Za tu svrhu se moe uvesti dodatna promenljiva c ija vrednost ukazuje na indeks prvog slobodnog elementa niza. Ostali problemi se mogu lake razumeti na konkretnom primeru, recimo, kompjuterske igre preko Interneta kojoj se igra ci mogu priklju citi ili je mogu napustiti u svakom trenutku. Ako pretpostavimo da klasa Igra c opisuje pojedina cne igra ce te igre, onda igra ci koji se trenutno igraju mogu biti predstavljeni nizom listaIgra ca tipa Igra c[]. Kako je broj igra ca promenljiv, potrebno je imati dodatnu promenljivu brojIgra ca koja sadri aktuelni broj igra ca koji u cestvuju u igri. Pod pretpostavkom da nikad ne ce biti vie od 10 igra ca istovremeno, relevantne deklaracije u programu su:
Igra c[] listaIgra ca = new Igra c[10]; // najvie 10 igra ca int brojIgra ca = 0; // na po cetku, broj igra ca je 0

Odgovaraju ci sadraj memorije posle izvravanja ovih deklaracija prikazan je na slici 3.6.
brojIgra ca

listaIgra ca

listaIgra ca[0] listaIgra ca[1] listaIgra ca[2]

listaIgra ca[9]

Slika 3.6: Po cetna slika niza listaIgra ca i promenljive brojIgra ca. Nakon to se nekoliko igra ca priklju ci igri, njihov aktuelni broj c e se nalaziti u promenljivoj brojIgra ca. Pored toga, elementi niza listaIgra ca na pozicijama od 0 do brojIgra ca - 1 ukazuju na objekte konkretnih igra ca koji u cestvuju u igri. Promenljiva brojIgra ca ima dakle dvostruku ulogu pored aktuelnog broja igra ca, ova promenljiva sadri i indeks prvog slobodnog elementa niza igra ca koji je neiskori cen. Registrovanje novog igra ca koji se priklju cio igri, recimo noviIgra c, u programu se postie dodavanjem tog igra ca nizu listaIgra ca na kraj niza:
listaIgra ca[brojIgra ca] = noviIgra c; brojIgra ca++; // novi igra c ide u prvi // slobodni element niza // aktuelni broj igra ca je ve ci za 1

3.3. Dinami cki nizovi

103

Kada neki igra c zavri igru, uklanjanje tog igra ca u programu mora se izvoditi tako da ne ostane rupa u nizu listaIgra ca. Zbog toga, ukoliko treba ukloniti igra ca koji se nalazi na k -toj poziciji u nizu listaIgra ca, postupak njegovog uklanjanja zavisi od toga da li je redosled aktivnih igra ca bitan ili ne. Ako redosled igra ca nije bitan, jedan na cin za uklanjanje k -tog igra ca je premetanje poslednjeg igra ca na k -to mesto u nizu listaIgra ca i smanjivanje vrednosti promenljive brojIgra ca za jedan:
listaIgra ca[k] = listaIgra ca[brojIgra ca - 1]; brojIgra ca--;

Igra c koji se nalazio na k -tom mestu nije vie u nizu listaIgra ca, jer se na to mesto premeta igra c na poslednjoj poziciji brojIgra ca - 1. S druge strane, ova pozicija nije vie logi cki poslednja i postaje slobodna za upisivanje novih igra ca, jer se promenljiva brojIgra ca umanjuje za jedan. U drugom slu caju kada je bitan redosled igra ca, radi uklanjanja k -tog igrac a se svi igra ci od indeksa k + 1 moraju pomeriti ulevo za jedno mesto. Drugim re cima, (k +1)-vi igra c dolazi na k -to mesto igra ca koji se uklanja, zatim (k +2)gi igra c dolazi na (k + 1)-vo mesto koje je oslobodeno prethodnim pomera njem, i tako dalje:
for (int i = k+1; i < brojIgra ca; i++) listaIgra ca[i - 1] = listaIgra ca[i]; brojIgra ca--;

Ve ci problem u radu sa nizovima ksne duine je to to se mora postaviti prili cno proizvoljna granica za broj elemenata niza. U prethodnom primeru kompjuterske igre, recimo, postavlja se pitanje ta uraditi ako se igri priklju ci vie od 10 igra ca kolika je duina niza listaIgra ca. O cigledno reenje je da se taj niz konstruie sa mnogo ve com duinom. Ali i ta ve ca duina ne garantuje da (na primer, zbog popularnosti igre) broj igra ca ne ce narasti toliko da opet ni ve ca duina niza nije dovoljna za registrovanje svih igra ca. Naravno, duina niza se uvek moe izabrati da bude vrlo velika i tako da bude dovoljna za sve prakti cne slu cajeve. Ovo reenje ipak nije zadovoljavaju ce, jer se moe desiti da veliki deo niza bude neiskori cen i da zato niz bespotrebno zauzima veliki memorijski prostor, moda spre cavaju ci izvravanje drugih programa na ra cunaru. Najbolje reenje ovog problema je to da niz po cetno ima skromnu duinu, a da se zatim moe produiti (ili smanjiti) po potrebi u programu. Ali kako ovo nije mogu ce, moramo se zadovoljiti priblinim reenjem koje je skoro isto tako dobro. Naime, podsetimo se da promenljiva tipa niza ne sadri elemente niza, nego samo ukazuje na objekat niza koji zapravo sadri njegove elemente. Ovaj niz se ne moe produiti, ali se moe konstruisati novi objekat dueg niza

104

3. N IZOVI

i promeniti vrednost promenljive koja ukazuje na objekat starog niza tako da ukazuje na objekat novog niza. Pored toga, naravno, elementi starog niza se moraju prekopirati u novi niz. Kona can rezultat ovog postupka bi ce dakle to da ista promenljiva tipa niza ukazuje na novi objekat dueg niza koji sadri sve elemente starog niza i ima dodatne slobodne elemente. Uz to, objekat starog niza bi ce automatski uklonjen iz memorije procedurom sakupljanja otpadaka, jer nijedna promenljiva vie ne ukazuje na objekat starog niza. Ako se ovaj pristup primeni u programu za kompjutersku igru, onda se u postupak za dodavanja novog igra ca moraju ugraditi prethodne napomene u slu caju kada je popunjen ceo niz listaIgra ca po cetne duine 10:
// Registrovanje novog igra ca kada je niz listaIgra ca popunjen if (brojIgra ca == listaIgra ca.length) { int novaDuina = 2 * listaIgra ca.length; Igra c[] noviNiz = Arrays.copyOf(listaIgra ca, novaDuina); listaIgra ca = noviNiz; } // Dodavanje novog igra ca u (novi) niz listaIgra ca listaIgra ca[brojIgra ca] = noviIgra c; brojIgra ca++;

Primetimo ovde da promenljiva listaIgra ca ukazuje na stari delimi cno popunjen niz ili na novi niz dvostruko dui od starog. Zbog toga, nakon izvravanja if naredbe, element listaIgra ca[brojIgra ca] je sigurno slobodan i moe mu se dodeliti novi igra c bez bojazni da c e se premaiti granicu niza na koji ukazuje promenljiva listaIgra ca. * * * Nizovi c ija se duina moe prilagoditi broju podataka koje treba da sadre nazivaju se dinami cki nizovi. U radu sa dinami ckimm nizovima su dozvoljene iste dve osnovne operacije kao i sa obi cnim nizovima: upisivanje vrednosti u element na datoj poziciji i c itanje vrednosti koja se nalazi u elementu na datoj poziciji. Ali pri tome ne postoji gornja granica za broj elemenata dinami ckog niza, osim naravno one koju name ce veli cina memorije ra cunara. Umesto ru cnog manipulisanja obi cnim nizom da bi se proizveo efekat dinami ckog niza, kako je to pokazano u primeru za program kompjuterske igre, u Javi se moe koristiti standardna klasa ArrayList iz paketa java.util. Klasa ArrayList opisuje objekte dinami ckih nizova koji u svakom trenutku imaju odredenu duinu, ali se ona automatski pove cava kada je to potrebno. U klasi ArrayList su denisani mnogi objektni metodi, ali oni najzna cajniji su:

3.3. Dinami cki nizovi

105

size() vra ca aktuelnu duinu niza tipa ArrayList. Dozvoljeni indeksi niza su samo oni u granicama od 0 do size() - 1. Pozivom podrazumevanog konstruktora u izrazu new ArrayList() konstruie se dinami cki niz duine nula. cava duinu niza za add(elem) dodaje element elem na kraj niza i pove jedan. Rezultat poziva ovog metoda je sli can onome koji se dobija za obi can niz a kada se uve ca vrednosti a.length za jedan i izvri naredba dodele a[a.length] = elem. ca vrednost elementa niza na poziciji n . Argument n mora get(n) vra biti ceo broj u intervalu od 0 do size() - 1, ina ce se program prekida usled greke. Rezultat poziva ovog metoda je sli can onome koji se dobija za obi can niz a kada se napie a[n], osim to se get(n) ne moe nalaziti na levoj strani znaka jednakosti u naredbi dodele. set(n, elem) dodeljuje vrednost elem onom elementu u nizu koji se nalazi na poziciji n , zamenjuju ci njegovu prethodnu vrednost. Argument n mora biti ceo broj u intervalu od 0 do size() - 1, ina ce se program prekida usled greke. Rezultat poziva ovog metoda je sli can onome koji se dobija za obi can niz a kada se napie a[n] = elem. remove(elem) uklanja dati element elem iz niza, ukoliko se vrednost datog elementa nalazi u nizu. Svi elementi iza uklonjenog elementa se pomeraju jedno mesto ulevo i duina niza se smanjuje za jedan. Ako se u nizu nalazi vie vrednosti datog elementa, uklanja se samo prvi primerak koji se nade ci sleva na desno. idu remove(n) uklanja n -ti element iz niza. Argument n mora biti ceo broj u intervalu od 0 do size() - 1. Svi elementi iza uklonjenog elementa se pomeraju jedno mesto ulevo i duina niza se smanjuje za jedan. indexOf(elem) pretrauje niz sleva na desno radi nalaenja vrednosti elementa elem u nizu. Ako se takva vrednost pronade u nizu, indeks prvog nadenog elementa se vra ca kao rezultat. U suprotnom slu caju, kao rezultat se vra ca vrednost 1. Koriste ci klasu ArrayList u primeru programa za kompjutersku igru, niz
listaIgra ca moe se deklarisati da bude dinami cki niz koji je po cetno pra-

zan:
ArrayList listaIgra ca = new ArrayList();

U tom slu caju se vie ne mora voditi ra cuna o duini niza, ve c se za dodavanje novog igra ca moe jednostavno pisati:
listaIgra ca.add(noviIgra c);

106

3. N IZOVI

Sli cno, za uklanjanje k -tog igra ca koristi se metod remove():


listaIgra ca.remove(k);

Ili, ako promenljiva isklju cenIgra c tipa Igra c ukazuje na objekat igra ca kojeg treba ukloniti, onda se moe pisati:
listaIgra ca.remove(isklju cenIgra c);

U ovim primerima je primena objektnih metoda klase ArrayList bila prirodna i o cigledna. Medutim, za metod get(n) kojim se dobija vrednost ele menta dinami ckog niza na poziciji n , moraju se razumeti jo neki detalji o objektima u Javi. Prvo, konstruktor ArrayList() konstruie dinami cki niz tipa Object[], odnosno elementi konstruisanog niza su tipa Object. Klasa Object sadri metode koji opisuju osnovne mogu cnosti svih objekata i o njoj c e biti vie re ci u odeljku 4.4. Svaka klasa u Javi automatski nasleduje klasu Object kojom se objekti predstavljaju u najirem smislu. Drugo, prema nac elu OOP koje se naziva princip podtipa, o c emu se detaljnije govori u odeljku 4.2, referenca na objekat bilo kog tipa moe biti dodeljena promenljivoj tipa Object. Zbog ovoga, kratko re ceno, u prethodnim primerima se ne mora voditi ra cuna o tipu elemenata dinami ckog niza. S druge strane, tip elemenata dinami ckog niza je bitan za kori cenje metoda get(). Naime, deklarisan tip rezultata metoda get() je Object, ali rezultat primene tog metoda na niz listaIgra ca jeste zapravo objekat tipa Igra c. Zbog toga, da bi se ita korisno moglo uraditi sa rezultatom poziva metoda get(), mora se izvriti eksplicitna konverzija tipa njegovog rezultata iz Object u Igra c:
Igra c pacer = (Igra c)listaIgra ca.get(n);

Na primer, ako klasa Igra c sadri objektni metod odigrajPotez() koji se poziva kada igra c treba da odigra svoj potez u igri, onda deo programa u kojem svaki aktivni igra c odigrava svoj potez moe biti:
for (int i = 0; i < listaIgra ca.size(); i++) { Igra c slede ciIgra c = (Igra c)listaIgra ca.get(i); slede ciIgra c.odigrajPotez(); }

Dve naredbe u telu prethodne for petlje se mogu spojiti u samo jednu, dodue komplikovaniju naredbu:
((Igra c)listaIgra ca.get(i)).odigrajPotez();

Ako se ova, naizgled zamrena, naredba ra clani na sastavne delove, uo cava se da se najpre dobija i -ti element niza listaIgra ca, zatim se vri konverzija u

3.3. Dinami cki nizovi

107

njegov pravi tip Igra c i, na kraju, poziva se metod odigrajPotez() za rezultuju ceg igra ca. Obratite panji i na to da se izraz (Igra c)listaIgra ca.get(i) mora navesti u zagradi zbog unapred denisanih pravila prioriteta operatora u Javi. U stvari, problem obavezne eksplicitne konverzije tipa prilikom c itanja vrednosti nekog elementa dinami ckog niza metodom get() vie ne postoji od verzije Java 5.0. Taj problem je prevaziden takozvanih para uvodenjem metrizovanih tipova. O njima se opirno govori u poglavlju 11, a u nastavku se govori samo najosnovnijim pojmovima na primeru klase ArrayList. Parametrizovani tip ArrayList<T> se moe koristiti umesto obi cnog tipa ArrayList. Parametar T ovde ozna cava bilo koji klasni tip (T ne sme biti primitivni tip). Dok klasa ArrayList predstavlja dinami cke nizove c iji su elementi tipa Object, klasa ArrayList<T> predstavlja dinami cke nizove c iji su elementi tipa T. Na primer, naredbom
ArrayList<Igra c> listaIgra ca = new ArrayList<Igra c>();

deklarie se promenljiva niza listaIgra ca tipa ArrayList<Igra c>, konstruie se prazan dinami cki niz c iji su elementi tipa Igra c i referenca na njega se dodeljuje promenljivoj listaIgra ca. Obratite panju na to da je ArrayList<Igra c> ime tipa kao i svako drugo i da se koristi na uobi cajen na cin suks <Igra c> je obi can deo imena tipa. Dodavanje elementa dinami ckom nizu parametrizovanog tipa, ili uklanjanje elementa iz njega, izvodi se na jednostavan na cin kao i ranije. Na primer,
listaIgra ca.add(noviIgra c);

ili
listaIgra ca.remove(isklju cenIgra c);

Upotreba parametrizovanih tipova dodatno olakava razvoj programa, jer Java prevodilac u fazi prevodenja programa moe lako proveriti da li su pro menljive noviIgra c i isklju cenIgra c zaista tipa Igra c i otkriti greku ukoliko to nije slu caj. Ali ne samo to, poto elementi niza listaIgra ca parametrizovanog tipa ArrayList<Igra c> moraju biti tipa Igra c, eksplicitna konverzija tipa nije vie neophodna prilikom c itanja vrednosti nekog elementa tog niza:
Igra c pacer = listaIgra ca.get(n);

Parametrizovani tipovi se mogu koristiti na potpuno isti na cin kao i obi cni tipovi za deklarisanje promenljivih i za tipove parametara ili rezultata nekog metoda. Jedino ograni cenje parametrizovanih tipova je to to parametar jednog takvog tipa ne moe biti primitivni tip. Na primer, tip ArrayList<int>

108

3. N IZOVI

nije dozvoljen. Medutim, to i nije veliki nedostatak zbog mogu cnosti pri mene klasa omota ca za primitivne tipove. Sve klase koji su omota ci primitivnih tipova mogu se koristiti za parametrizovane tipove. Objekat, recimo, tipa ArrayList<Double> je dinami cki niz c iji elementi sadre objekte tipa Double. Kako objekat klasnog tipa Double sadri vrednost primitivnog tipa double, to je skoro isto kao da ArrayList<Double> predstavlja dinami cki niz c iji su elementi realni brojevi tipa double. Na primer, iza naredbi
double x; ArrayList<Double> nizBrojeva = new ArrayList<Double>();

u programu se metodom add() moe dodati vrednost promenljive x na kraj niza nizBrojeva:
nizBrojeva.add( new Double(x) );

Ili, zbog autopakovanja i raspakovanja, moe se c ak kra ce pisati


nizBrojeva.add( x );

jer se sve neophodne konverzije obavljaju automatski.

Primer: telefonski imenik


Jedan konkretan primer parametrizovanog dinami ckog niza je struktura podataka koju c ini telefonski imenik. Telefonski imenik je niz stavki koje se sastoje od dva dela: imena osobe i njenog telefonskog broja. Pojedine stavke telefonskog imenika obrazuju otprilike objekte slede ce klase:
public class TelStavka { String ime; // ime osobe String telBroj; // telefonski broj osobe }

Ceo telefonski imenik u programu se moe prosto realizovati kao niz elemenata, pri c emu je svaki element niza jedan objekat tipa TelStavka. Kako se broj stavki u telefonskom imeniku ne moe unapred ni blizu predvideti, prirodno se name ce reprezentacija telefonskog imenika u obliku dinami ckog niza stavki. Kratko re ceno, telefonski imenik kao struktura podataka u programu je dinami cki niz tipa ArrayList<TelStavka>. Medutim, pored ovakve eme za c uvanje podataka u telefonskom imeniku, neophodno je obezbediti i metode kojima se realizuju osnovne operacije nad podacima u telefonskom imeniku. Imaju ci ova zapaanja u vidu, klasa TelImenik u nastavku predstavlja telefonski imenik kao strukturu podataka. Ta klasa sadri polje imenik koje ukazuje na dinami cki niz sa podacima u imeniku, kao i slede ce metode kojima se realizuju osnovne operacije nad podacima u imeniku:

3.3. Dinami cki nizovi

109

Metod na diBroj() za dato ime osobe pronalazi njegov telefonski broj u imeniku. Metod dodajStavku() dodaje novu stavku ime/broj u imenik. U ovom metodu se najpre proverava da li se dato ime nalazi u imeniku. Ako je to slu caj, stari broj se zamenjuje datim brojem. U suprotnom slu caju, nova stavka se dodaje u imenik. Metod ukloniStavku() uklanja stavku sa datim imenom osobe iz imenika. Ako se takva stavka ne pronade u imeniku, nita se posebno ne preduzima.
import java.util.*; class TelImenik { private ArrayList<TelStavka> imenik; // niz stavki

// Konstruktor klase konstruie prazan imenik public TelImenik() { imenik = new ArrayList<TelStavka>(); } private int na diStavku(String imeOsobe) { for (int i = 0; i < imenik.size(); i++) { TelStavka s = imenik.get(i); // Da li i-ta osoba ima dato ime? if (s.ime.equals(imeOsobe)) return i; // i-ta osoba ima dato ime } return -1; // nema osobe sa datim imenom } public String na diBroj(String imeOsobe) { int i = na diStavku(imeOsobe); if (i >= 0) // osoba je u imeniku? // Ako jeste, vratiti njen tel. broj return imenik.get(i).telBroj; else // Ako nije, vratiti referencu null return null; } public void dodajStavku(String imeOsobe, String brojOsobe) { if (imeOsobe == null || brojOsobe == null) {

110

3. N IZOVI

System.out.println("Greka: prazno ime ili broj!"); return; } int i = na diStavku(imeOsobe); if (i >= 0) // osoba se nalazi u imeniku? // Ako da, zameniti stari broj novim brojem imenik.get(i).telBroj = brojOsobe; else { // Ako ne, dodati novu stavku ime/broj u imenik TelStavka s = new TelStavka(); s.ime = imeOsobe; s.telBroj = brojOsobe; imenik.add(s); } } public void ukloniStavku(String imeOsobe) { int i = na diStavku(imeOsobe); if (i >= 0) // osoba se nalazi u imeniku? // Ako da, ukloniti njenu stavku iz imenika imenik.remove(i); } }

Obratite panju na to da je u klasi TelImenik denisan i pomo cni metod


na diStavku() u kojem se primenjuje linerana pretraga za nalaenje pozicije

stavke sa datim imenom u imeniku. Metod na diStavku() je privatan metod, jer je potreban samo za interno kori cenje od strane ostalih javnih metoda klase TelImenik. Klasa TelImenik predstavlja objekte telefonskih imenika kojima se bez ograni cenja na veli cinu mogu dodavati imena i brojevi osoba. Ova klasa dodatno obezbeduje mogu cnost uklanjanja stavke iz imenika i nalaenje tele fonskog broja osobe na osnovu datog imena osobe. Jedan primer testiranja klase TelImenik je prikazan u slede cem glavnom metodu:
public static void main(String[] args) { TelImenik mojImenik = new TelImenik(); mojImenik.dodajStavku("Pera", null); mojImenik.dodajStavku("Pera", "111-1111"); mojImenik.dodajStavku("ika", "222-2222"); mojImenik.dodajStavku("Laza", "333-3333"); mojImenik.dodajStavku("Mira", "444-4444"); System.out.println("Laza: " + mojImenik.na diBroj("Laza")); mojImenik.dodajStavku("Laza", "999-9999"); System.out.println("Laza: " + mojImenik.na diBroj("Laza")); System.out.println("Pera: " + mojImenik.na diBroj("Pera"));

3.3. Dinami cki nizovi

111

mojImenik.ukloniStavku("ika"); System.out.println("ika: " + mojImenik.na diBroj("ika")); System.out.println("Mira: " + mojImenik.na diBroj("Mira")); }

G LAVA 4

N ASLEIVANJE KL ASA

Mogu cnost nasledivanja klasa u objektno orijentisanom programiranju je jedna od njegovih najve cih prednosti u odnosu na proceduralno programiranje. Primenom na cela nasledivanja klasa omogu cava se pisanje novih klasa koje proiruju postoje ce klase. U ovom poglavlju se najpre pokazuju prednosti nasledivanja klasa uopte, a zatim se govori o pojedinostima tog koncepta u Javi.

4.1 Osnovni pojmovi


Neka klasa predstavlja skup objekata koji imaju zajedni cku strukturu i mogu cnosti. Klasa odreduje strukturu objekata na osnovu objektnih polja, dok mogu cnosti objekata klasa odreduje preko objektnih metoda. Ova ideja vodilja objektno orijentisanog programiranja (OOP) nije dodue velika novost, jer se neto sli cno moe posti ci i primenom drugih, tradicionalnijih principa programiranja. Centralna ideja objektno orijentisanog programiranja, koja ga izdvaja od ostalih paradigmi programiranja, jeste da se klasama mogu izraziti sli cnosti medu cke oso objektima koji imaju neke, ali ne sve, zajedni bine. U objektno orijentisanom programiranju, nova klasa se moe napraviti na osnovu postoje ce klase. To zna ci da nova klasa proiruje postoje cu klasu i nasleduje sva njena polja i metode. Preneseno na objekte nove klase, ovo zna ci da oni nasleduje sve atribute i mogu cnosti postoje cih objekata. Ovaj koncept u OOP se naziva nasledivanje klasa ili kra ce samo nasledivanje . Mogu cnost proirivanja postoje ce klase radi pravljenja nove klase ima mnoge prednosti od kojih su najvanije polimorzam i apstrakcija (o c emu se detaljnije govori u odeljku 4.5 i odeljku 5.2), kao i viekratna upotrebljivost i olakano menjanje programskog koda.

114

4. N ASLEIVANJE KL ASA

Treba imati u vidu da terminologija u vezi sa nasledivanjem klasa nije standardizovana. Uglavnom iz istorijskih razloga, ali i li cnih aniteta autora, u upotrebi su razli citi termini koji su sinonimi za polaznu klasu i klasu naslednicu: osnovna i proirena klasa, bazna i izvedena klasa, natklasa i potklasa, klasa-roditelj i klasa-dete, pa i nadredena i podredena klasa. U svakodnevnom radu, naro cito za programere koji su tek po celi da se upoznaju sa objektno orijentisanim pristupom, nasledivanje se koristi uglav nom za menjanje ve c postoje ce klase koju treba prilagoditi sa nekoliko izmena ili dopuna. To je mnogo c e ca situacija nego pravljenje kolekcije klasa i proirenih klasa od po cetka. Denicija nove klase koja proiruje postoje cu klasu ne razlikuje se mnogo od uobi cajene denicije obi cne klase:
modifikatori class nova-klasa extends stara-klasa { . . // Izmene ili dopune postoje ce klase . }

U ovom optem obliku, nova-klasa je ime nove klase koja se denie, a stara-klasa je ime postoje ce klase koja se proiruje. Telo nove klase izmedu viti castih zagrada ima istu strukturu kao telo neke uobi cajene klase. Prema tome, jedina novost kod nasledivanja klase je pisanje slubene re ci extends i imena postoje ce klase iza imena nove klase. Na primer, kostur denicije klase B koja nasleduje klasu A ima ovaj izgled:
class B extends A { . . // Novi clanovi klase B ili izmene . // postoje cih clanova klase A . }

Sutinu i prednosti nasledivanja klasa je najbolje objasniti na nekom pri meru. Posmatrajmo zato program za obra cun plata radnika neke rme i razmotrimo klasu koja moe predstavljati te radnike u kontekstu obra cuna njihovih plata. Ako zanemarimo enkapsulaciju podataka da ne bismo primer komplikovali sa geter i seter metodima, prvi pokuaj denisanja klase Radnik moe izgledati na primer:
public class Radnik { String ime; long jmbg; long ra cun; // ime i prezime radnika // jedinstven broj radnika // bankovni ra cun radnika

4.1. Osnovni pojmovi

115

double plata;

// plata radnika

public void uplatiPlatu() { System.out.print("Plata za " + ime + ", broj " + jmbg); System.out.println(" upla cena na ra cun " + ra cun); } public double izra cunajPlatu() { // Obra cunavanje mese cne plate radnika } }

Na prvi pogled izgleda da klasa Radnik dobro opisuje radnike za obrac unavanje njihovih mese cnih plata. Svaki radnik ima svoje ime, jedinstven broj i ra cun u banci. Jedina dilema postoji oko metoda koji obra cunava platu radnika. Problem je u tome to u rmi moe raditi vie vrsta radnika u pogledu na cina obra cunavanja njihove mese cne plate. Pretpostavimo da u rmi neki radnici imaju ksnu mese cnu zaradu, dok su drugi pla ceni po radnim satima po odredenoj ceni sata. Reenje koje nam se odmah name ce, naro cito ako dolazimo iz sveta proceduralnog programiranja, jeste da klasi Radnik dodamo indikator koji ukazuje na to da li se radi o jednoj ili drugoj vrsti radnika. Ako dodamo logi cko polje pla cenPoSatu, na primer, onda se njegova vrednost moe koristiti u metodu za obra cun mese cne plate da bi se ispravno izra cunala plata konkretnog radnika:
public double izra cunajPlatu() { if (pla cenPoSatu) // Obra cunavanje plate radnika pla cenog po satu . . . } else { // Obra cunavanje plate radnika sa fiksnom zaradom . . . } }

Ovo reenje medutim nije dobro sa aspekta objektno orijentisanog pro gramiranja, jer se fakti cki koristi jedna klasa za predstavljanje dva tipa objekata. Pored ovog koncepcijskog nedostatka, ve ci problem nastaje kada treba neto menjati u programu. ta ako rma po cne da zapoljava i radnike koji se pla caju na tre ci na cin na primer, po danu bez obzira na broj radnih sati? Onda logi cki indikator nije vie dovoljan, nego ga treba zameniti celobrojnim poljem, recimo tipRadnika, c ija vrednost ukazuje na to o kom tipu radnika se radi. Na primer, vrednost 0 odreduje radnika sa ksnom mese cnom za radom, vrednost 1 odreduje radnika pla cenog po satu i vrednost 2 odreduje

116

4. N ASLEIVANJE KL ASA

radnika pla cenog po danu. Ovakvo reenje zahteva, pored zamene indikatora, ozbiljne modikacije prethodnog metoda za obra cun mese cne plate:
public double izra cunajPlatu() switch (tipRadnika) { 0 : // Obra cunavanje plate . . . break; 1 : // Obra cunavanje plate . . . break; 2 : // Obra cunavanje plate . . . break; } } {

radnika sa fiksnom zaradom

radnika pla cenog po satu

radnika pla cenog po danu

Naravno, svaki put kada se u rmi zaposle novi radnici sa druga cijim nac inom pla canja, mora se menjati ovaj metod za obra cun plate i dodati novi slu caj. Bolje reenje, ali jo uvek ne i ono pravo, jeste da se klasa Radnik podeli u dve (ili vie) klasa koje odgovaraju vrstama radnika prema na cinu obra cuna njihove plate. Na primer, za radnike koji su mese cno pla ceni ksno ili po radnim satima, mogu se denisati dve klase na slede ci na cin:
public class RadnikPla cenFiksno { String ime; long jmbg; long ra cun; double plata; // // // // ime i prezime radnika jedinstven broj radnika bankovni ra cun radnika mese cna plata radnika

public void uplatiPlatu() { System.out.print("Plata za " + ime + ", broj " + jmbg); System.out.println(" upla cena na ra cun " + ra cun); } public double izra cunajPlatu() { return plata; } } public class RadnikPla cenPoSatu { String ime; // ime i prezime radnika

4.1. Osnovni pojmovi

117

long jmbg; long ra cun; double brojSati; double cenaSata;

// // // //

jedinstven broj radnika bankovni ra cun radnika broj radnih sati radnika iznos za jedan radni sat

public void uplatiPlatu() { System.out.print("Plata za " + ime + ", broj " + jmbg); System.out.println(" upla cena na ra cun " + ra cun); } public double izra cunajPlatu() { return brojSati * cenaSata; } }

Ovo reenje je bolje od prvog, jer mada za novu vrstu radnika treba dodati novu klasu, bar se ne moraju menjati postoje ce klase za stare vrste radnika. Ali, nedostatak ovog reenja je to se moraju ponavljati zajedni cka polja i metodi u svim klasama. Naime, bez obzira da li su radnici pla ceni ksno ili po satu (ili na neki drugi na cin), oni pripadaju kategoriji radnika i zato imaju mnoga zajedni cka svojstva. To je tipi cni slu caj kada se nasledivanje klasa moe iskoristiti kako za eliminisanje ponavljanja programskog koda, tako i za pisanje programa koji se lako mogu menjati. Najbolje reenje za obra cun plata radnika je dakle da se izdvoje zajedni cka svojstva radnika u baznu klasu, a njihova posebna svojstva ostave za nasledene klase. Na primer:
public class Radnik { String ime; long jmbg; long ra cun; // ime i prezime radnika // jedinstven broj radnika // bankovni ra cun radnika

public void uplatiPlatu() { System.out.print("Plata za " + ime + ", broj " + jmbg); System.out.println(" upla cena na ra cun " + ra cun); } } public class RadnikPla cenFiksno extends Radnik { double plata; // mese cna plata radnika

public double izra cunajPlatu() { return plata; } }

118

4. N ASLEIVANJE KL ASA

public class RadnikPla cenPoSatu extends Radnik { double brojSati; double cenaSata; // broj radnih sati radnika // iznos za jedan radni sat

public double izra cunajPlatu() { return brojSati * cenaSata; } }

Obratite panju na to da radnici ne dele isti metod izra cunajPlatu(). Svaka klasa koja nasleduje klasu Radnik ima svoj poseban metod za obra cun plate. To medutim nije ponavljanje programskog koda, jer svaku klasu na slednicu upravo karakterie logi cki razli cit na cin izra cunavanja plate odgovaraju ce vrste radnika. U optem slu caju, relacija jeste predstavlja relativno jednostavan i pouzdan test da li treba primeniti nasledivanje klasa u nekoj situaciji. Naime, kod svakog nasledivanja mora biti slu caj da objekat klase naslednice jeste i objekat nasledene klase. Ako se moe re ci da postoji taj odnos, onda je nasledivanje klasa primereno za reenje odgovaraju ceg problema. U prethodnom primeru recimo, radnik pla cen ksno na mese cnom nivou jeste radnik. Sli cno, radnik pla cen po radnim satima jeste radnik. Zato je primereno pisati klase RadnikPla cenFiksno i RadnikPla cenPoSatu tako da nasleduju klasu Radnik. S druge strane, pretpostavimo da za svakog radnika elimo da c uvamo podatak o datumu zaposlenja. Poto imamo standardnu klasu Date u Javi za predstavljanje datuma, da li je u ovom slu caju dobra ideja da klasu Radnik napiemo kao naslednicu klase Date? Odgovor je negativan, jer naravno ne vai odnos da radnik jeste datum. Odnos koji postoji u ovom slu caju izmedu radnika i datuma je da radnik ima datum zaposlenja. Ako objekat ima neki atribut, taj atribut treba realizovati kao polje u klasi tog objekta. Zato za predstavljanje datuma zaposlenja svakog radnika, klasa Radnik treba da ima polje tipa Date, a ne da nasleduje klasu Date.

4.2 Hijerarhija klasa


Proirena klasa kod nasledivanja moe dopuniti strukturu i mogu cnosti klase koju nasleduje. Ona moe i zameniti ili modikovati nasledene mogu c nosti, ali ne i nasledenu strukturu. Prema optem obliku denicije za klasu

4.2. Hijerarhija klasa

119

naslednicu, moemo zaklju citi da u Javi klasa moe direktno nasledivati samo jednu klasu. Medutim, nekoliko klasa moe direktno nasledivati istu klasu. Ove klase naslednice dele neke osobine koje nasleduju od zajedni cke klase roditelja. Drugim re cima, nasledivanjem se uspostavlja hijerarhijska relacija izmedu srodnih klasa. Relacija nasledivanja srodnih klasa slikovito se prikazuje klasnim dija gramom u kojem se klasa-dete nalazi ispod klase-roditelja i s njom je povezana strelicom. Tako su na slici 4.1 klase B, C i D direktne naslednice zajedni cke klase A, a klasa E je direktna naslednica klase D. Kao to moemo naslutiti sa slike 4.1, dubina nasledivanja klasa u Javi nije ograni cena i moe se prostirati na nekoliko generacija klasa. Ovo je na slici 4.1 ilustrovano klasom E koja nasleduje klasu D, a ova sa svoje strane nasleduje klasu A. U ovom slu caju se smatra da je klasa E indirektna naslednica klase A. Cela kolekcija klasa povezanih relacijom nasledivanja obrazuje na ovaj na cin hijerarhiju klasa.

klasa A

klasa B

klasa C

klasa D

klasa E Slika 4.1: Hijerarhija klasa.

Razmotrimo jedan konkretniji primer. Pretpostavimo da treba napisati program za evidenciju motornih vozila i da zato u programu treba denisati klasu Vozilo za predstavljanje svih vrsta motornih vozila. Poto su putni cka i teretna vozila posebne vrste motornih vozila, ona se mogu predstaviti klasama koje su naslednice klase Vozilo. Putni cka i teretna vozila se dalje mogu podeliti na auta, motocikle, kamione i tako dalje, c ime se obrazuje hijerarhija klasa prikazana na slici 4.2. Osnovna klasa Vozilo treba dakle da sadri polja i metode koji su zajedni cki za sva vozila. Oni obuhvataju, na primer, polja za vlasnika i brojeve motora i tablice, kao i metod za prenos vlasnitva (vlasnici su predstavljeni posebnom klasom Osoba):

120

4. N ASLEIVANJE KL ASA

Vozilo

Putni ckoVozilo

TeretnoVozilo

Auto

Motocikl

Kamion

Slika 4.2: Hijerarhija klasa motornih vozila.

public class Vozilo { Osoba vlasnik; int brojMotora; String brojTablice; . . . public void promeniVlasnika(Osoba noviVlasnik) { . . . } . . . }

Ostale klase u hijerarhiji mogu zatim posluiti za dodavanje polja i metoda koji su speci cni za odgovaraju cu vrstu vozila. Na primer:
public class Putni ckoVozilo extends Vozilo { int brojVrata; Color boja; . . . } public class TeretnoVozilo extends Vozilo { int brojOsovina; . . . } public class Auto extends Putni ckoVozilo { int brojSedita; . . .

4.2. Hijerarhija klasa

121

} public class Motocikl extends Putni ckoVozilo { boolean sediteSaStrane; . . . } public class Kamion extends TeretnoVozilo { int nosivost; . . . }

Ako je bmw promenljiva klasnog tipa Auto koja je deklarisana i inicijalizovana naredbom
Auto bmw = new Auto();

onda se efekat ove naredbe deklaracije u memoriji ra cunara moe videti na slici 4.3.
bmw
brojSedita brojVrata boja vlasnik brojMotora brojTablice

promeniVlasnika

. . .
Slika 4.3: Objekat klase Auto na koga ukazuje bmw. Poto je brojSedita objektno polje klase Auto, ono je naravno deo objekta na koga ukazuje promenljiva bmw. Zato u programu moemo pisati, na primer:
bmw.brojSedita = 2;

Ali kako Auto nasleduje Putni ckoVozilo, objekat klase Auto na koga uka zuje promenljiva bmw sadri i sva polja iz klase Putni ckoVozilo. To zna ci da je uz promenljivu bmw ispravno koristiti i polja brojVrata i boja koja su denisana u klasi-roditelju Putni ckoVozilo. Na primer:

122

4. N ASLEIVANJE KL ASA

System.out.println(bmw.brojVrata + bmw.boja);

Najzad, klasa Putni ckoVozilo nasleduje klasu Vozilo, to zna ci da objek ti klase Putni ckoVozilo (u koje spadaju objekti klase Auto) sadre sva polja iz klase-roditelja Vozilo. Prema tome, u programu je takode ispravno koristi polja
bmw.vlasnik bmw.brojMotora bmw.brojTablice

kao i metod bmw.promeniVlasnika(). Ova strana objektno orijentisanog programiranja se slae sa na cinom razmiljanja na koji smo navikli u svakodnevnom ivotu. Naime, auta su vrsta putni ckih vozila, a putni cka vozila su vrsta vozila u optem smislu. Drugim re cima, objekat tipa Auto je automatski objekat tipa Putni ckoVozilo, kao i objekat tipa Vozilo. Zato neki objekat tipa Auto ima, pored svojih speci cnih osobina, sve osobine roditeljskog tipa Putni ckoVozilo, kao i sve osobine tipa Vozilo. Osobine nekog tipa su predstavljene poljima i metodama odgovaraju ce klase, pa je zato u programu dozvoljeno koristiti sva prethodno pomenuta polja i metode sa objektom na koga ukazuje promenljiva bmw. Vana speci cnost objektno orijentisanog programiranja u ovom pogledu ti ce se promenljivih klasnog tipa. Ako je denisana klasa A, onda je poznato da promenljiva x klasnog tipa A ima vrednosti koje su reference na objekte klase A. Dodatak ove c injenice u kontekstu nasledivanja klasa je da promenljiva x moe sadrati i reference na objekte svih klasa koje (direktno ili indirektno) nasleduju klasu A. Ova mogu cnost da promenljiva optijeg tipa ukazuje na objekat speci cnijeg podtipa je deo optijeg objektno orijentisanog na cela koje se naziva princip podtipa: objekat nekog podtipa (klase-deteta) moe se uvek koristiti tamo gde se o cekuje objekat njegovog nadtipa (klase-roditelja). Princip podtipa je posledica c injenice da tretiraju ci objekat nekog podtipa kao da je objekat njegovog nadtipa, moemo iskoristiti samo njegove umanjene osobine, ali ne i dodatne speci cnije osobine. Naime, nasledivanjem klase se izvede noj klasi mogu samo dodati ili zameniti polja i metodi, a nikad se ne mogu oduzeti. Prakti cna posledica primene principa podtipa na hijerarhiju klase Vozilo jeste da referenca na objekat tipa Auto moe biti dodeljena promenljivoj tipa Putni ckoVozilo ili Vozilo. Ispravno je pisati, na primer,
Vozilo mojeVozilo; mojeVozilo = bmw;

ili kra ce

4.2. Hijerarhija klasa

123

Vozilo mojeVozilo = bmw;

ili c ak
Vozilo mojeVozilo = new Auto();

Efekat izvravanja bilo kog od ovih programskih fragmenata je isti: promenljiva mojeVozilo tipa Vozilo sadri referencu na objekat tipa Auto koji je podtip tipa Vozilo. U programu se c ak moe proveravati da li dati objekat pripada odredenoj klasi. Za tu svrhu se koristi logi cki operator instanceof koji se u optem obliku pie na slede ci na cin:
ime-promenljive instanceof ime-klase

gde promenljiva na levoj strani mora biti klasnog tipa. Ako ova promenljiva sadri referencu na objekat koji pripada klasi ime-klase, rezultat operatora instanceof je true; u suprotnom slu caju, rezultat je false. Na primer, naredbom
if (mojeVozilo instanceof Auto) ...

ispituje se da li objekat na koga ukazuje promenljiva mojeVozilo zaista pripada klasi Auto. Radi dobijanja rezultata operatora instanceof, u svakom objektu se, izmedu ostalog, nalazi informacija o tome kojoj klasi objekat pripada. Drugim re cima, prilikom konstruisanja novog objekta u hip memoriji, pored uobic ajene inicijalizacije rezervisane memorije za njega, upisuje se i podatak o denisanoj klasi kojoj novi objekat pripada. Obrnut smer principa podtipa ne vai, pa dodela reference objekta optijeg tipa promenljivoj speci cnijeg tipa nije dozvoljena. Na primer, naredba dodele:
bmw = mojeVozilo;

nije ispravna, jer promenljiva mojeVozilo moe sadrati reference na objekte drugih tipova vozila koji nisu auta. Tako, ako promenljiva mojeVozilo sadri referencu na objekat klase Kamion, onda bi nakon izvravanja ove naredbe dodele i promenljiva bmw sadrala referencu na isti objekat klase Kamion. Ali onda bi upotreba polja bmw.brojSedita, recimo, dovela do greke u programu zato to objekat na koga trenutno ukazuje promenljiva bmw ne sadri polje brojSedita, jer to polje nije denisano u klasi Kamion. Princip podtipa podse ca na pravila konverzije primitivnih tipova. Na primer, nije dozvoljeno dodeliti neku vrednost tipa int promenljivoj tipa short, jer nije svaka vrednost tipa int istovremeno i vrednost tipa short. Sli cno, ne

124

4. N ASLEIVANJE KL ASA

moe se neka vrednost tipa Vozilo dodeliti promenljivoj tipa Auto, jer svako vozilo nije i auto. Kao i kod konverzije vrednosti primitivnih tipova, reenje je i ovde upotreba eksplicitne konverzije tipa. Ako je u programu poznato na neki na cin da promenljiva mojeVozilo zaista ukazuje na objekat tipa Auto, ispravno je pisati recimo
bmw = (Auto)mojeVozilo;

ili c ak
((Auto)mojeVozilo).brojSedita = 4;

Ono to se napie u programu ne mora naravno odgovarati pravom sluc aju u toku izrvavanja programa, pa se za klasne tipove prilikom izvravanja programa proverava da li je zahtevana konverzija tipa ispravna. Tako, ako promenljiva mojeVozilo ukazuje na objekat tipa Kamion, onda bi eksplicitna konverzija (Auto)mojeVozilo izazvala greku i prekid programa. Da ne bi dolazilo do greke tokom izvravanja programa kada stvarni tip objekata na koga ukazuje promenljiva klasnog tipa nije unapred poznat, ekplicitna konverzija tipa se c esto kombinuje sa operatorom instanceof. Na primer:
if (mojeVozilo instanceof Auto) ((Auto)mojeVozilo).brojSedita = 4; else if (mojeVozilo instanceof Motocikl) ((Motocikl)mojeVozilo).sediteSaStrane = false; else if (mojeVozilo instanceof Kamion) ((Kamion)mojeVozilo).nosivost = 2;

Obratite panju na to da u deniciji hijerarhije klase Vozilo nismo namerno koristili specikatore pristupa za polja da ne bismo komplikovali optu sliku nasledivanja. Ali svi principi o enkapsulaciji podataka koji vae za obi cne klase, stoje bez izmene i za nasledene klase. To zna ci generalno da polja neke klase treba da budu privatna, a pristup njima treba obezbediti preko javnih geter i seter metoda. Isto tako, samo metodi koji su deo interfejsa klase treba da budu javni, a oni koji su deo interne implementacije treba da budu privatni. Pored specikatora pristupa private i public za c lanove klase, treba voditi ra cuna da kod nasledivanja dolazi u obzir i tre ci specikator pristupa protected. Clan klase koji je protected (zati cen) moe se koristiti u direktno ili indirektno nasledenoj klasi. Dodatno, zati cen c lan se moe koristiti u klasama koje pripadaju istom paketu kao klasa koja sadri taj c lan. Podsetimo se da se c lan bez ikakvog specikatora pristupa moe koristiti samo u klasama iz istog paketa. To zna ci da je pristup protected striktno liberalniji

4.3. Slubena re c super

125

od toga, jer dozvoljava kori cenje zati cenog c lana u klasama iz istog paketa i u nasledenim klasama koje nisu deo istog paketa. Uzmimo na primer polje boja u klasi Putni ckoVozilo. Zbog enkapsulacije podataka bi to polje trebalo da bude privatno da ne bi moglo da se menja izvan te klase. Naravno, dobijanje vrednosti tog polja iz druge klase bi se obezbedilo geter metodom. Ali ako je klasa Putni ckoVozilo predvidena za proirivanje, polje boja bismo mogli da deklariemo da bude zati ceno kako bi dozvolili klasama naslednicama, ali ne i svim klasama, da mogu slobodno menjati to polje. Jo bolje reenje bi bilo obezbediti protected seter metod za to polje u klasi Putni ckoVozilo. U tom metodu bi se proveravalo, na primer, da li kod dodele boje za putni cko vozilo to ima smisla. S obzirom na specikatore pristupa za c lanove klase, kod nasledivanja klasa treba naglasiti jo jednu c injenicu: iako objekti klase naslednice sadre privatne c lanove iz nasledene klase, u klasi naslednici ti c lanovi nisu direktno dostupni. Prema tome, privatni c lanovi neke klase ne mogu se direktno koristiti nigde, c ak ni u klasi naslednici, osim u samoj klasi u kojoj su denisani. Naravno, ako je potrebno, pristup privatnim poljima neke klase iz drugih nepovezanih klasa treba obezbediti javnim geter i seter metodima. A ukoliko se eli omogu citi restriktivniji pristup privatnim poljima samo iz klasa naslednica, onda geter i seter metodi za ta polja treba da budu denisani kao zati ceni.

4.3 Slubena re c super


Klasa B koja nasleduje postoje cu klasu A moe imati dodatne ili zame njene c lanove (polja i metode) u odnosu na nasledenu klasu A. Za kori cenje dodatnih c lanova u klasi naslednici B nema nejasno ca, jer takvi c lanovi ne postoje u nasledenoj klasi A, pa se mogu nedvosmisleno prepoznati na osnovu svojih prostih imena. U slu caju zamenjenih c lanova koji su denisani u klasi naslednici B, a ve c postoje u nasledenoj klasi A, moe se postaviti pitanje na ta se misli kada se u klasi naslednici B koriste njihova imena: da li na primerak denisan u klasi naslednici B ili na onaj denisan u nasledenoj klasi A? Generalan odgovor na ovo pitanje je da se kori cenje prostog imena nekog c lana klase uvek odnosi na primerak u klasi u kojoj je taj c lan denisan. Ali, u klasi naslednici B isti c lan koji postoji i u nasledenoj klasi A ne zamenjuje taj c lan iz nasledene klase A, nego ga samo zaklanja. Taj zaklonjeni c lan iz nasledene klase A se i dalje moe koristiti u klasi naslednici B uz slubenu re c super:
super.zaklonjen- clan

126

4. N ASLEIVANJE KL ASA

Prema tome, slubena re c super se koristi za pristup c lanovima nasledene klase koji su zaklonjeni c lanovima klase naslednice. Na primer, super.x se uvek odnosi na objektno polje sa imenom x u nasledenoj klasi. Zaklanjanje polja u klasi naslednici se retko koristi, ali ako je to slu caj, onda objekat klase naslednice sadri zapravo dva polja sa istim imenom: jedno koje je denisano u samoj klasi naslednici i drugo koje je denisano u nasledenoj klasi. Novo polje u klasi naslednici ne zamenjuje staro polje sa istim imenom u nasledenoj klasi, nego ga samo zaklanja time to se podrazumeva da se svaka prosta upotreba tog imena u klasi naslednici odnosi na primerak polja denisanog u klasi naslednici. Sli cno, ako se u klasi naslednici denie metod koji ima isti potpis kao neki metod u nasledenoj klasi, metod iz nasledene klase je zaklonjen u klasi naslednici na isti na cin. U ovom slu caju kaemo da metod u klasi naslednici nadja cava metod iz nasledene klase. Ali ako je nadja can metod iz nasledene klase potreban u klasi naslednici, opet se moe koristiti slubena re c super uz njega. Kao jedan primer primene re ci super, razmotrimo klasu Dete koju nasleduje klasa U cenik. Klasa Dete slui, recimo, za registrovanje podataka poje dine dece, dok klasa U cenik slui za registrovanje podataka pojedinih u cenika osnovnih kola. Budu ci da je svaki u cenik ujedno i dete, prirodno je da klasa U cenik nasleduje klasu Dete:
public class Dete { private String ime; private int uzrast; // Konstruktor public Dete(String ime, int uzrast) { this.ime = ime; this.uzrast = uzrast; } // Metod prikai() public void prikai() { System.out.println(); System.out.println("Ime: " + ime); System.out.println("Uzrast: " + uzrast); } } public class U cenik extends Dete { private String kola;

4.3. Slubena re c super

127

private int razred; // Konstruktor public U cenik(String ime, int uzrast, String kola, int razred){ super(ime, uzrast); this.kola = kola; this.razred = razred; System.out.println("Konstruisan u cenik ... "); prikai(); } // Metod prikai() public void prikai() { super.prikai(); System.out.println("kola: " + kola); System.out.println("Razred: " + razred); } }

U klasi Dete su denisana dva privatna polja ime i uzrast, konstruktor za inicijalizovanje ovih polja objekta deteta, kao i metod prikai() koji prikazuje individualne podatke nekog deteta. U proirenoj klasi U cenik su dodata dva privatna polja kola i razred, konstruktor za inicijalizovanje svih polja objekta u cenika, kao i drugi metod prikai() koji prikazuje individualne podatke nekog u cenika. Kako metod prikai() u klasi U cenik ima isti potpis kao metod sa istim imenom u klasi Dete, u klasi U cenik ova u ceni cka verzija tog metoda nadja cava njegovu de cju verziju sa istim potpisom. Zato se poziv metoda prikai() na kraju konstruktora klase U cenik odnosi na taj metod koji je denisan u klasi U cenik. cenik je predviden Metod prikai() u klasi U za prikazivanje podataka o u ceniku. Ali kako objekat u cenika sadri privatna polja ime i uzrast nasledene klase Dete, ovaj metod nema pristup tim poljima i ne moe direktno prikazati njihove vrednosti. Zbog toga se naredbom
super.prikai();

u metodu prikai() klase U cenik najpre poziva metod prikai() nasledene klase Dete. Ovaj nadja can metod ima pristup poljima ime i uzrast, pa moe prikazati njihove vrednosti. Na primer, izvravanjem naredbe deklaracije
U cenik u = new U cenik("Laza Lazi c", 16, "gimnazija", 2);

dobijaju se slede ci podaci na ekranu:


Konstruisan u cenik ...

128

4. N ASLEIVANJE KL ASA

Ime: Uzrast: kola: Razred:

Laza Lazi c 16 gimnazija 2

Primetimo jo da bi bilo pogreno da se u metodu prikai() klase U cenik poziva nadja can metod klase Dete bez re ci super ispred metoda prikai(). To bi onda zna cilo da se zapravo rekurzivno poziva metod prikai() klase U cenik, pa bi se dobio beskona can lanac poziva tog metoda. Ovaj mali primer nasledivanja pokazuje, u stvari, glavnu primenu slu bene re ci super u optem slu caju. Naime, ona se koristi u slu cajevima kada novi metod sa istim potpisom u klasi naslednici ne treba da potpuno zameni funkcionalnost nasledenog metoda, ve c novi metod treba da proiri funkcio nalnost nasledenog metoda. U novom metodu se tada moe koristiti slubena re c super za pozivanje nadja canog metoda iz nasledene klase, a dodatnim naredbama se moe proiriti njegova funkcionalnost. U prethodnom primeru je metod prikai() za prikazivanje podataka u cenika proirivao funkcionalnost metoda prikai() za prikazivanje podataka deteta. Neko bi mogao prigovoriti da u prethodnom primeru nismo ni morali da koristimo re c super. Naime, da smo dozvolili da pristup poljima ime i uzrast nasledene klase Dete bude javan ili zati cen, u metodu prikai() proirene klase U cenik smo mogli da direkno prikaemo njihove vrednosti. Ali obratite panju na to da smo kori cenjem re ci super u prethodnom primeru mogli da proirimo funkcionalnost metoda prikai() klase U cenik c ak i da nismo znali kako je napisan nadja can metod u klasi Dete. A to je tipi cni slu caj u praksi, jer programeri vrlo c esto proiruju ne cije gotove klase c iji izvorni tekst ne poznaju ili ne mogu da menjaju. Radi kompletnosti, pomenimo na kraju i mogu cnost spre cavanja da neki metod u nasledenoj klasi bude nadja can. To se obezbeduje navodenjem mo dikatora final u zaglavlju metoda. Na primer:
public final void nekiMetod() { ... }

Podsetimo se da se modikatorom final za promenljive (i polja) onemogu cava promena njihove vrednosti nakon njihove inicijalizacije. U slu caju metoda, modikatorom final se onemogu cava nadja cavanje takvog metoda u bilo kojoj klasi koja nasleduje onu u kojoj je metod denisan. U stvari, modikator final moemo koristiti i za klase. Ako se final nalazi u zaglavlju denicije neke klase, to zna ci da se ta klasa ne moe naslediti. Na primer:
public final class NekaKlasa { ... }

4.3. Slubena re c super

129

Druga cije gledano, final klasa je kona cni zavretak grane u stablu neke hijerarhije klasa. Na neki na cin final klasa se moe smatrati generalizacijom final metoda, jer svi njeni metodi automatski postaju final metodi. Ali sa druge strane to ne vai za njena polja ona ne dobijaju nikakvo dodatno specijalno zna cenje u final klasi. Razlozi zbog kojih se obi cno koriste final klase i metodi su bezbednost i objektno orijentisani dizajn. Na primer, ako su neke klase ili metodi toliko vani da bi njihovo menjanje moglo ugroziti ispravan rad celog programa, onda ih treba denisati sa modikatorom final. Upravo je ovo razlog zato su standardne klase System i String denisane da budu final klase. Drugo, ako neka klasa koncepcijski nema smisla da ima naslednicu, ona se moe denisati da bude final klasa. Razloge za upotrebu modikatora final u svakom konkretnom slu caju treba ipak dobro odmeriti, jer taj modikator ima negativisti cku ulogu ograni cavanja podrazumevanih mehanizama u Javi.

Konstruktori u klasama naslednicama


Konstruktor tehni cki ne pripada klasi, pa se ni ne moe naslediti u klasi naslednici. To zna ci da, ako nijedan konstruktor nije denisan u klasi naslednici, toj klasi se dodaje podrazumevani konstruktor bez obzira na konstruktore u nasledenoj klasi. Drugim re cima, ako se eli neki poseban konstruktor u klasi naslednici, on se mora pisati od po cetka. To moe predstavljati problem, ukoliko treba ponoviti sve inicijalizacije onog dela konstruisanog objekta klase naslednice koje obavlja konstruktor nasledene klase. To moe biti i nemogu c zadatak, ukoliko se nemaju detalji rada konstruktora nasledene klase (jer izvorni tekst nasledene klase nije do stupan), ili konstruktor nasledene klase inicijalizuje privatna polja nasledene klase. Reenje ovog problema se sastoji u mogu cnosti pozivanja konstruktora nasledene klase upotrebom slubene re ci super. Ova namena te re ci je pove zana sa njenom glavnom namenom za pozivanje nadja canog metoda nasledene klase u smislu da se poziva konstruktor nasledene klase. Medutim, na cin pozivanja konstruktora nasledene klase je druga ciji od uobi cajenog poziva nadja canog metoda, jer se ni konstruktori ne pozivaju kao ostali metodi. To u optem slu caju izgleda kao da je re c super ime nekog metoda, iako ona to nije:
super( lista-argumenata )

130

4. N ASLEIVANJE KL ASA

Na primer, u konstruktoru klase U cenik prethodnog primera, pozvali smo konstruktor nasledene klase Dete radi inicijalizacije polja ime i uzrast kon struisanog objekta klase U cenik:
super(ime, uzrast);

Obratite panju na to da poziv konstruktora nasledene klase upotrebom re ci super mora biti prva naredba u konstruktoru klase naslednice. Radi potpunosti napomenimo i da ako se konstruktor nasledene klase ne poziva ek splicitno na ovaj na cin, onda se automatski poziva podrazumevani konstruktor nasledene klase bez argumenata. U stvari, potpun postupak za medusobno pozivanje konstruktora i inicija lizaciju objektnih polja prilikom konstruisanja nekog objekta operatorom new sastoji se od ovih koraka: Ako je prva naredba pozvanog konstruktora obi cna naredba, odnosno nije poziv drugog konstruktora pomo cu slubenih re ci this ili super, implicitno se dodaje poziv super() radi pozivanja (podrazumevanog) konstruktora nasledene klase bez argumenata. Posle zavretka tog po ziva se inicijalizuju objektna polja i nastavlja se sa izvravanjem prvog konstruktora. Ako prva naredba pozvanog konstruktora predstavlja poziv konstruktora nasledene klase pomo cu slubene re ci super, poziva se taj kon struktor nasledene klase. Posle zavretka tog poziva se inicijalizuju ob jektna polja i nastavlja se sa izvravanjem prvog konstruktora. Ako prva naredba pozvanog konstruktora predstavlja poziv nadja canog ci nadkonstruktora pomo cu slubene re ci this, poziva se odgovaraju ja cani konstruktor aktuelne klase. Posle zavretka tog poziva se odmah nastavlja sa izvravanjem prvog konstruktora. Poziv konstruktora nasledene klase se izvrava unutar nadja canog konstruktora, bilo ek splicitno ili implicitno, tako da se i inicijalizacija objektnih polja tamo zavrava.

4.4 Klasa Object


Glavna odlika objektno orijentisanog programiranja je mogu cnost nasledivanja klasa i denisanja nove klase na osnovu stare klase. Nova klasa na sleduje sve atribute i mogu cnosti postoje ce klase, ali ih moe modikovati ili dodati nove.

4.4. Klasa Object

131

Objektno orijentisani jezik Java je karakteristi can po tome to sadri specijalnu klasu Object koja se nalazi na vrhu ogromne hijerarhije klasa koja obuhvata sve druge klase u Javi. Klasa Object denie osnovne mogu cnosti koje moraju imati svi objekti: proveravanje jednakosti objekta sa drugim objektima, generisanje jedinstvenog kodnog broja objekta, konvertovanja objekta u string, kloniranje objekta i tako dalje. Stablo nasledivanja u Javi je organizovano tako da svaka klasa, osim spe cijalne klase Object, jeste (direktna ili indirektna) naslednica neke klase. Naime, neka klasa koja nije eksplicitno denisana kao naslednica druge klase, automatski postaje naslednica klase Object. To zna ci da denicija svake klase koja ne sadri re c extends, na primer,
public class NekaKlasa { ... }

potpuno je ekvivalentna sa
public class NekaKlasa extends Object { ... }

Kako je dakle svaka klasa u Javi naslednica klase Object, to prema principu podtipa zna ci da promenljiva deklarisanog tipa Object moe zapravo sadrati referencu na objekat bilo kog tipa. Ova c injenica je iskori cena u Javi za realizaciju standardnih struktura podataka koje su denisane tako da sadre elemente tipa Object. Ali kako je svaki objekat ujedno i instanca tipa Object, ove strukture podataka mogu zapravo sadrati objekte proizvoljnog tipa. Jedan primer ovih struktura su dinami cki nizovi tipa ArrayList o kojima smo govorili u odeljku 3.3. U klasi Object je denisano nekoliko objektnih metoda koje dakle nasleduje svaka klasa. Ovi metodi se zato mogu bez izmena koristiti u svakoj klasi, ali se mogu i nadja cati ukoliko njihova osnovna funkcionalnost nije odgovaraju ca. Ovde c emo pomenuti samo one metode iz klase Object koji su korisni za jednostavnije primene ostali metodi se koriste za sloenije, vienitne programe i njihov opis se moe na ci u zvani cnoj dokumentaciji jezika Java.

equals
Metod equals() vra ca logi cku vrednost ta cno ili neta cno prema tome da li je neki objekat koji je naveden kao argument metoda jednak onom objektu za koji je metod pozvan. Preciznije, ako su o1 i o2 promenljive klasnog tipa Object, u klasi Object je ovaj metod denisan tako da je o1.equals(o2) ekvivalentno sa o1 == o2. Ovaj metod dakle jednostavno proverava da li dve promenljive klasnog tipa Object ukazuju na isti objekat. Ovaj pojam jednakosti dva objekta druge konkretnije klase obi cno nije odgovaraju ci, pa u toj klasi treba nadja cati ovaj metod radi realizacije pojma jednakosti njenih objekata.

132

4. N ASLEIVANJE KL ASA

Na primer, dva deteta predstavljena klasom Dete iz prethodnog odeljka mogu se smatrati jednakim ukoliko imaju isto ime i uzrast. Zato u klasi Dete treba nadja cati osnovni metod equals() na slede ci na cin:
public boolean equals(Object obj) { if (obj == null || !(obj instanceof Dete)) return false; Dete drugoDete = (Dete)obj; // konverzija tipa obj u Dete if (this.ime == drugoDete.ime && this.uzrast == drugoDete.uzrast) // Drugo dete ima isto ime i uzrast kao ovo dete, // pa se podrazumeva da su deca jednaki objekti. return true; else return false; }

Drugi primer je metod equals() u standardnoj klasi String kojim se dva stringa (tj. objekta klase String) smatraju jednakim ako se sastoje od ta cno istih nizova znakova (tj. istih znakova u istom redosledu):
String ime = "Pera"; . . . if (ime.equals(korisni ckoIme)) login(korisni ckoIme); . . .

Obratite panju na to da ovo nije logi cki ekvivalentno sa ovim testom:


if (ime == korisni ckoIme) // Pogreno!

Ovom if naredbom se proverava da li dve promenljive klasnog tipa String ukazuju na isti string, to je dovoljno ali nije neophodno da dva string objekta budu jednaka.

hashCode
Metod hashCode() kao rezultat daje kodni broj objekta za koji se poziva. Ovaj nasumi cno izabran ceo broj (koji moe biti negativan) slui za identikaciju objekta i popularno se naziva njegov he kd. He kodovi se koriste za rad sa objektima u takozvanim he tabelama. To su naro cite strukture podataka koje omogu cavaju ekasno c uvanje i nalaenje objekata. Osnovni metod hashCode() u klasi Object proizvodi jedinstven he kd za svaki objekat. Drugim re cima, jednaki objekti imaju isti he kd i razli citi objekti imaju razli cite he kdove. Pri tome se pod jednako cu objekata podrazumeva ona jednakost koju realizuje metod equals() klase Object. Zbog toga, ukoliko se u klasi nadja cava metod equals(), mora se nadja cati i metod

4.4. Klasa Object

133

hashCode(). Uslov koji nadja cana verzija metoda hashCode() mora zadovo-

ljiti, radi ispravne realizacije he tabela, donekle je oslabljen u odnosu na onaj koji zadovoljava podrazumevana verzija klase Object. Naime, (novo)jednaki objekti moraju imati isti he kd, ali razli citi objekti ne moraju dobiti razli cit he kd.

toString
Metod toString() kao rezultat daje string (objekat tipa String) c ija je uloga da predstavlja objekat, za koji je metod pozvan, u obliku stringa. Zna caj metoda toString() se sastoji u tome to ukoliko se neki objekat koristi u kontekstu u kojem se o cekuje string, recimo kada se objekat prikazuje na ekranu, onda se taj objekat automatski konvertuje u string pozivanjem metoda toString(). Verzija metoda toString() koja je denisana u klasi Object, za objekat za koji je ovaj metod pozvan, proizvodi string koji se sastoji od imena klase kojoj taj objekat pripada, zatim znaka @ i, na kraju, he koda tog objekta u heksadekadnom zapisu. Na primer, ako promenljiva mojeDete ukazuje na neki objekat klase Dete iz prethodnog odeljka, onda bi se kao rezultat poziva
mojeDete.toString()

dobio string oblika "Dete@163b91". Poto ovo nije mnogo korisno, u sopstvenim klasama treba denisati novi metod toString() koji c e nadja cati ovu nasledenu verziju. Na primer, u klasi Dete iz prethodnog odeljka moe se dodati slede ci metod:
public String toString() { return ime + " (" + uzrast + ")"; }

Ako promenljiva mojeDete ukazuje na neki objekat klase Dete, onda bi se ovaj metod automatski pozivao za konvertovanje tog objekta u string ukoliko se mojeDete koristi u kontekstu u kojem se o cekuje string. Na primer, izvravanjem ovog programskog fragmenta:
Dete mojeDete = new Dete("Aca", 10); System.out.print("Moj mali je " + mojeDete); System.out.println(", ljubi ga majka.");

na ekranu se dobija rezultat


Moj mali je Aca (10), ljubi ga majka.

134

4. N ASLEIVANJE KL ASA

clone
Metod clone() proizvodi kopiju objekta za koji je pozvan. Pravljenje kopije nekog objekta izgleda prili cno jednostavan zadatak, jer na prvi pogled treba samo kopirati vrednosti svih polja u drugu instancu iste klase. Ali ta ako polja originalnog objekta sadre reference na objekte neke druge klase? Kopiranjem ovih referenci c e polja kloniranog objekta takode ukazivati na iste objekte druge klase. To moda nije efekat koji se eli moda svi objekti druge klase na koje ukazuju polja kloniranog objekta treba da budu isto tako nezavisne kopije. Reenje ovog problema nije tako jednostavno i prevazilazi okvire ove knjige. Ovde c emo samo pomenuti da se rezultati prethodna dva pristupa kloniranja nazivaju plitka kopija i duboka kopija originalnog objekta. Verzija metoda clone() u klasi Object, koju nasleduju sve klase, proizvodi plitku kopiju objekta.

4.5 Polimorzam
Tri stuba na koja se oslanja objektno orijentisano programiranje su enkapsulacija, nasledivanje i polimorzam. Do sada smo upoznali prva dva koncepta, a sada c emo objasniti polimorzam. Re c polimorzam ima korene u gr ckom jeziku i zna ci otprilike mnotvo oblika (na gr ckom poli zna ci mnogo i morf zna ci oblik). U Javi se polimorzam manifestuje na vie na cina. Jedna forma polimorzma je princip podtipa po kojem promenljiva klasnog tipa moe sadrati reference na objekte svog deklarisanog tipa i svakog njegovog podtipa. Druga forma polimorzma odnosi se na razli cite metode koji imaju isto ime. U jednom slu caju, razli citi metodi imaju isto ime, ali razli cite liste parametara. To su onda preoptere ceni metodi o kojima smo govorili na strani 38. Verzija preoptere cenog metoda, koja se zapravo poziva u konkretnoj naredbi poziva tog metoda, odreduje se na osnovu liste argumenata poziva. To se moe razreiti u fazi prevodenja programa, pa se kae da se za pozive pre optere cenih metoda primenjuje stati cko vezivanje (ili rano vezivanje). Posmatrajmo primer klase A i njene naslednice klase B u kojima je denisano nekoliko verzija preoptere cenog metoda m(). Sve verzije ovog metoda samo prikazuju poruku na ekranu koja identikuje pozvani metod:
public class A { public void m() { System.out.println("m()");

4.5. Polimorzam

135

} } public class B extends A { public void m(int x) { System.out.println("m(int x)"); } public void m(String y) { System.out.println("m(String y)"); } }

U klasi A je denisan preoptere cen metod m() bez parametara. Klasa B nasleduje ovaj metod i dodatno denie jo dve njegove verzije:
m(int x) m(String y)

Primetimo da obe ove verzije metoda m imaju po jedan parametar razli citog tipa. U slede cem programskom fragmentu se pozivaju sve tri verzije ovog metoda za objekat klase B:
B b = new B(); b.m(); b.m(17); b.m("niska");

Java prevodilac moe da, na osnovu argumenata koji su navedeni u pozivima metoda m(), nedvosmisleno odredi koju verziju preoptere cenog metoda treba izvriti u svakom slu caju. To potvrduje i rezultat izvravanja prethodnog programskog fragmenta koji se dobija na ekranu:
m() m(int x) m(String y)

U drugom mogu cem slu caju za vie metoda sa istim imenom, razli citi metodi imaju isti potpis (isto ime i iste liste parametara) u klasama naslednicama. To su onda nadja cani metodi o kojima smo govorili u odeljku 4.3. Kod nadja canih metoda se odluka o tome koji metod treba zapravo pozvati donosi u vreme izvravanja na osnovu stvarnog tipa objekta za koji je metod pozvan. Ovaj mehanizam vezivanja poziva metoda za odgovaraju ce telo metoda koje c e se izvriti, naziva se dinami cko vezivanje (ili kasno vezivanje) poto se primenjuje u fazi izvravanja programa, a ne u fazi prevodenja programa. Da bismo ovo razjasnili, razmotrimo slede ci primer hijerarhije klasa ku cnih ljubimaca:

136

4. N ASLEIVANJE KL ASA

public class Ku cniLjubimac { public void koSamJa() { System.out.println("Ja sam ku cni ljubimac."); } } public class Pas extends Ku cniLjubimac { public void koSamJa() { System.out.println("Ja sam pas."); } } public class Ma cka extends Ku cniLjubimac { public void koSamJa() { System.out.println("Ja sam ma cka."); } }

U baznoj klasi Ku cniLjubimac je denisan metod koSamJa(), a on je nadja can u izvedenim klasama Pas i Ma cka kako bi se prikazala odgovaraju ca poruka na ekranu zavisno od vrste ku cnog ljubimca. Obratite panju na to da ako je ljubimac promenljiva klasnog tipa Ku cniLjubimac, onda se poziv metoda koSamJa() u naredbi
ljubimac.koSamJa();

ne moe razreiti u fazi prevodenja programa. To je posledica principa pod tipa, jer promenljiva ljubimac moe ukazivati na objekat bilo kog tipa iz hijerarhije ku cnih ljubimaca: Ku cniLjubimac, Pas ili Ma cka. Zbog toga se odluka o tome koju verziju metoda koSamJa() treba pozvati mora odloiti za fazu izvravanja programa, jer c e se samo tada znati stvarni tip objekta na koga ljubimac ukazuje. A na osnovu tog tipa c e se onda pozvati i odgovaraju ca verzija metoda koSamJa(). Na primer, posle izvravanja ovog programskog fragmenta:
Ku cniLjubimac ljubimac1 = new Ku cniLjubimac(); Ku cniLjubimac ljubimac2 = new Pas(); Ku cniLjubimac ljubimac3 = new Ma cka(); ljubimac1.koSamJa(); ljubimac2.koSamJa(); ljubimac3.koSamJa();

na ekranu se dobija rezultat


Ja sam ku cni ljubimac. Ja sam pas. Ja sam ma cka.

4.5. Polimorzam

137

U ovom primeru su deklarisane tri promenljive ljubimac1, ljubimac2 i ljubimac3 istog tipa Ku cniLjubimac. Medutim, samo prva promenljiva uka zuje na objekat tipa Ku cniLjubimac, dok ostale dve ukazuju na objekte podtipova tog tipa. Na osnovu rezultata izvravanja programskog fragmenta moemo zaklju citi da je verzija metoda koSamJa() koja se poziva, stvarno ona koja odgovara tipu objekata na koje ukazuju tri promenljive istog deklarisanog tipa Ku cniLjubimac. Dinami cko vezivanje metoda obavlja se dakle po jednostavnom pravilu: nadja can metod koji se poziva za neku klasnu promenljivu zavisi od stvarnog tipa objekta na koga ukazuje ta promenljiva, bez obzira na njen deklarisan tip. Zahvaljuju ci ovom aspektu polimorzma moe se na ekasan na cin manipulisati velikim brojem objekata u istoj hijerarhiji klasa. Ako jedan niz predstavlja populaciju svih ku cnih ljubimaca koje dre itelji nekog grada od 100000 stanovnika, na primer,
Ku cniLjubimac[] populacija = new Ku cniLjubimac[100000];

i ako pojedina cni elementi niza ukazuju na objekte tipova Ku cniLjubimac, Pas ili Ma cka, onda pojedina cne vrste ljubimaca moemo prikazati u obi cnoj petlji:
for (Ku cniLjubimac ljubimac : populacija) { ljubimac.koSamJa(); }

Pored toga, ako se hijerahiji klasa ku cnih ljubimaca kasnije doda nova izvedena klasa, na primer,
public class Kanarinac extends Ku cniLjubimac { public void koSamJa() { System.out.println("Ja sam kanarinac."); } }

i ako neki elementi niza populacija mogu sada ukazivati i na objekte nove klase Kanarinac, onda prethodna petlja za prikazivanje vrste svih ku cnih ljubimaca ne mora uopte da se menja. Ova opta mogu cnost, da se moe pisati programski kd koji c e ispravno uraditi neto to nije c ak ni zamiljeno u vreme njegovog pisanja, jeste verovatno najzna cajnija osobina polimorzma.

G LAVA 5

P OSEBNE KL ASE I INTERFEJSI

Pored obi cnih klasa koje smo do sada upoznali, u Javi se mogu koristiti i druge vrste klasa c ija prava vrednost dolazi do izraaja tek u sloenijim primenama objektno orijentisanog programiranja. U ovom poglavlju se govori o takvim posebnim vrstama klasa. U stvari, najpre se govori o jednostavnom konceptu nabrojivih tipova koji su vani i za svakodnevno programiranje. Nabrojivim tipom se prosto predstavlja ograni cen (mali) skup vrednosti. Mogu cnost denisanja nabrojivih tipova je relativno skoro dodata programskom jeziku Java (od verzije 5.0), ali mnogi programeri smatraju da su nabrojivi tipovi trebali biti deo Jave od samog po cetka. Druga tema ovog poglavlja su apstraktne klase. U objektno orijentisanom programiranju je c esto potrebno denisati opte mogu cnosti datog apstraktnog entiteta bez navodenja konkretnih implementacionih detalja svake od njegovih mogu cnosti. U takvim slu cajevima se moe koristiti apstraktna klasa na vrhu hijerarhije klasa za navodenje najoptijih zajedni ckih mogu cnosti u vidu apstraktnih metoda, dok se u klasama naslednicama apstraktne klase ovi apstraktni metodi mogu nadja cati konkretnim metodima sa svim detaljima. Tre ci deo ovog poglavlja posve cen je vanom konceptu interfejsa. Interfejsi u Javi su jo jedan na cin da se opie ta objekti u programu mogu da urade, bez denisanja na cina koji pokazuje kako treba to da urade. Neka klasa moe implementirati jedan ili vie interfejsa, a objekti ovih implementiraju cih klasa onda poseduju sve mogu cnosti koje su navedene u implementiranim interfejsima. Na kraju, ugnjedene klase su tehni cki neto sloeniji koncept one se deniu unutar drugih klasa i njihovi metodi mogu koristiti polja obuhvatajuc ih klasa. Ugnjedene klase su naro cito korisne za pisanje programskog koda koji slui za rukovanje dogadajima gra ckog korisni ckog interfejsa. Java programeri verovatno ne ce morati da piu svoje apstraktne klase, in-

140

5. P OSEBNE KL ASE I INTERFEJSI

terfejse i ugnjedene klase sve dok ne dodu cke pisanja vrlo sloenih pro do ta grama ili biblioteka klasa. Ali c ak i za svakodnevno programiranje, a pogotovo za programiranje gra ckih aplikacija, svi programeri moraju da razumeju ove naprednije koncepte, jer se na njima zasniva kori cenje mnogih standardnih tehnika u Javi. Prema tome, da bi Java programeri bili uspeni u svom poslu, moraju poznavati pomenute naprednije mogu cnosti objektno orijentisanog programiranja u Javi bar na upotrebnom nivou.

5.1 Nabrojivi tipovi


Programski jezik Java sadri osam ugradenih primitivnih tipova i poten cijalno veliki broj drugih tipova koji se u programu deniu klasama. Vrednosti primitivnih tipova, kao i dozvoljene operacije nad njima, ugradeni su u sm jezik. Vrednosti klasnih tipova su objekti deniu cih klasa, a dozvoljene operacije nad njima su denisani metodi u tim klasama. U Javi postoji jo jedan na cin za denisanje novih tipova koji se nazivaju nabrojivi tipovi (engl. enumerated types ili kra ce enums). Pretpostavimo da je u programu potrebno predstaviti c etiri godinja doba: prole ce, leto, jesen i zimu. Ove vrednosti bi se naravno mogle kodirati celim brojevima 1, 2, 3 i 4, ili slovima P, L, J i Z, ili c ak objektima neke klase. Ali nedostatak ovakvog pristupa je to to je svako kodiranje podlono grekama. Naime, u programu je vrlo lako uvesti neku pogrenu vrednost za godinje doba: recimo, broj 0 ili slovo z ili dodatni objekat pored c etiri pravih. Bolje reenje je denisanje novog nabrojivog tipa:
public enum GodinjeDoba { PROLE CE, LETO, JESEN, ZIMA }

Na ovaj na cin se denie nabrojivi tip GodinjeDoba koji se sastoji od c etiri vrednosti c ija su imena PROLE CE, LETO, JESEN i ZIMA. Po konvenciji kao i za obi cne konstante, vrednosti nabrojivog tipa se piu velikim slovima i sa donjom crtom ukoliko se sastoje od vie re ci. U pojednostavljenom optem obliku, denicija nabrojivog tipa se pie na slede ci na cin:
enum ime-tipa { lista-konstanti }

U deniciji nabrojivog tipa, njegovo ime se navodi iza slubene re ci enum kao prost identikator ime-tipa. Vrednosti nabrojivig tipa, medusobno odvo jene zapetama, navode se unutar viti castih zagrada kao lista-konstanti. Pri tome, svaka konstanta u listi mora biti prost identikator. Denicija nabrojivog tipa ne moe stajati unutar metoda, jer nabrojivi tip mora biti c lan klase. Tehni cki, nabrojivi tip je specijalna vrsta ugnjedene

5.1. Nabrojivi tipovi

141

klase koja sadri kona can broj instanci (nabrojive konstante) koje su navedene unutar viti castih zagrada u deniciji nabrojivog tipa.1 Tako, u prethodnom primeru, klasa GodinjeDoba ima ta cno c etiri instance i nije mogu ce konstruisati nove. Nakon to se denie nabrojivi tip, on se moe koristiti na svakom mestu u programu gde je dozvoljeno pisati tip podataka. Na primer, mogu se deklarisati promenljive nabrojivog tipa:
GodinjeDoba modnaSezona;

Promenljiva modnaSezona moe imati jednu od c etiri vrednosti nabrojivog tipa GodinjeDoba ili specijalnu vrednost null. Ove vrednosti se promenljivoj modnaSezona mogu dodeliti naredbom dodele, pri c emu se mora koristiti puno ime konstante tipa GodinjeDoba. Na primer:
modnaSezona = GodinjeDoba.LETO;

Promenljive nabrojivog tipa se mogu koristiti i u drugim naredbama gde to ima smisla. Na primer:
if (modnaSezona == GodinjeDoba.LETO) System.out.println("Krpice za jun, jul i avgust."); else System.out.println(modnaSezona);

Iako se nabrojive konstante tehni cki smatraju da su objekti, primetimo da se u if naredbi ne koristi metod equals() nego operator ==. To je zato to svaki nabrojivi tip ima ksne, unapred poznate instance i zato je za proveru njihove jednakosti dovoljno uporediti njihove reference. Primetimo isto tako da se drugim metodom println() prikazuje samo prosto ime konstante nabrojivog tipa (bez preksa GodinjeDoba). Nabrojivi tip je zapravo klasa, a svaka konstanta nabrojivog tipa je public ce klase, iako se nabrojive konstante ne piu final static polje odgovaraju sa ovim modikatorima. Vrednosti ovih polja su reference na objekte koji pripadaju klasi nabrojivog tipa, a jedan takav objekat odgovara svakoj nabrojivoj konstanti. To su jedini objekti klase nabrojivog tipa i novi se nikako ne mogu konstruisati. Prema tome, ovi objekti predstavljaju mogu ce vrednosti nabrojivog tipa, a nabrojive konstante su polja koje ukazuju na te objekte. Poto se tehni cki podrazumeva da nabrojivi tipovi predstavljaju klase, oni pored nabrojivih konstanti mogu sadrati polja, metode i konstruktore kao i obi cne klase. Naravno, konstruktori se jedino pozivaju kada se konstruiu
1 Denicija nabrojivog tipa ne mora biti ugnjedena u nekoj klasi, ve c se moe nalaziti u

svojoj posebnoj datoteci. Na primer, denicija nabrojivog tipa GodinjeDoba moe stajati u datoteci GodinjeDoba.java.

142

5. P OSEBNE KL ASE I INTERFEJSI

imenovane konstante koje su navedene kao vrednosti nabrojivih tipova. Ukoliko nabrojivi tip sadri dodatne c lanove, oni se navode iza liste konstanti koja se mora zavriti ta ckom-zarez. Na primer:
public enum GodinjeDoba { PROLECE(P), LETO(L), JESEN(J), ZIMA(Z); private char skra cenica; // polje

private GodinjeDoba(char skra cenica) { // konstruktor this.skra cenica = skra cenica; } public char getSkra cenica() { return skra cenica; } } // geter metod

Svi nabrojivi tipovi nasleduju jednu klasu Enum i zato se u radu s njima mogu koristiti metodi koji su denisani u klasi Enum. Najkorisniji od njih je metod toString() koji vra ca ime nabrojive konstante u obliku stringa. Na primer:
modnaSezona = GodinjeDoba.LETO; System.out.println(modnaSezona);

Ovde se u naredbi println, kao za svaku promenljivu klasnog tipa, implicitno poziva metod modnaSezona.toString() i dobija kao rezultat string "LETO" (koji se na ekranu prikazuje bez navodnika). cki metod Obrnutu funkciju metoda toString() u klasi Enum obavlja stati valueOf() c ije je zaglavlje:
static Enum valueOf(Class nabrojiviTip, String ime)

Metod valueOf() kao rezultat vra ca nabrojivu konstantu datog nabrojivog tipa s datim imenom. Na primer:
Scanner tastatura = new Scanner(System.in); System.out.print("Unesite godinje doba: "); String g = tastatura.nextLine(); modnaSezona = (GodinjeDoba) Enum.valueOf( GodinjeDoba.class, g.toUpperCase());

Ukoliko se prilikom izvravanja ovog programskog fragmenta preko tastature unese string zima, promenljiva modnaSezona kao vrednost dobija nabrojivu konstantu GodinjeDoba.ZIMA.

5.1. Nabrojivi tipovi

143

Objektni metod ordinal() u klasi Enum kao rezultat daje redni broj nabrojive konstante, broje ci od nule, u listi konstanti navedenih u deniciji nabrojivog tipa. Na primer, GodinjeDoba.PROLE CE.ordinal() daje vrednost 0 tipa int, GodinjeDoba.LETO.ordinal() daje vrednost 1 tipa int i tako dalje. Najzad, svaki nabrojivi tip ima stati cki metod values() koji kao rezultat vra ca niz svih vrednosti nabrojivog tipa. Na primer, niz gd od c etiri elementa sa vrednostima
GodinjeDoba.PROLE CE GodinjeDoba.LETO GodinjeDoba.JESEN GodinjeDoba.ZIMA

moe se denisati na slede ci na cin:


GodinjeDoba[] gd = GodinjeDoba.values();

Metod values() se c esto koristi u paru sa for-each petljom kada je potrebno obraditi sve konstante nabrojivog tipa. Izvravanjem ovog programskog fragmenta, na primer,
enum Dan { PONEDELJAK, UTORAK, SREDA, CETVRTAK, PETAK, SUBOTA, NEDELJA}; for (Dan d : Dan.values()) { System.out.print(d); System.out.print(" je dan pod rednim brojem "); System.out.println(d.ordinal()); }

na ekranu se kao rezultat dobija:


PONEDELJAK je dan pod rednim brojem 0 UTORAK je dan pod rednim brojem 1 SREDA je dan pod rednim brojem 2 CETVRTAK je dan pod rednim brojem 3 PETAK je dan pod rednim brojem 4 SUBOTA je dan pod rednim brojem 5 NEDELJA je dan pod rednim brojem 6

Nabrojivi tipovi se mogu koristiti i u paru sa switch naredbom. Preciznije, izraz u switch naredbi moe biti nabrojivog tipa. Konstante uz klauzule case onda moraju biti nabrojive konstante odgovaraju ceg tipa, pri c emu se moraju pisati njihova prosta imena a ne puna. Na primer:
switch (modnaSezona) { case PROLE CE: // pogreno je GodinjeDoba.PROLECE System.out.println("Krpice za mart, april i maj."); break;

144

5. P OSEBNE KL ASE I INTERFEJSI

case LETO: System.out.println("Krpice za jun, jul i avgust."); break; case JESEN: System.out.println("Krpice za sept., okt. i nov."); break; case ZIMA: System.out.println("Krpice za dec., jan. i feb."); break; }

5.2 Apstraktne klase


Da bismo bolje razumeli apstraktne klase, razmotrimo jedan program kojim se crtaju razni geometrijski oblici na ekranu medu ci tro kojima su mogu uglovi, pravougaonici i krugovi. Primer objekata ovog programa je prikazan na slici 5.1.

Slika 5.1: Geometrijski oblici programa za crtanje. Nakon analize problema i imaju ci u vidu objektno orijentisani pristup, recimo da smo odlu cili da deniemo tri klase Trougao, Pravougaonik i Krug koje c e predstavljati tri tipa mogu cih geometrijskih oblika u programu. Ove tri klase c e nasledivati zajedni c ku klasu GeomOblik u kojoj su denisana zajed ni cka svojstva koje poseduju sva tri oblika. Klasnim dijagramom na slici 5.2 je prikazano stablo nasledivanja klase GeomOblik.
GeomOblik

Trougao

Pravougaonik

Krug

Slika 5.2: Hijerarhija klasa geometrijskih oblika.

5.2. Apstraktne klase

145

Zajedni cka klasa GeomOblik moe imati, izmedu ostalog, objektna polja za boju, poziciju na ekranu i veli cinu geometrijskog oblika, kao i objektni metod za crtanje geometrijskog oblika. Na primer:
public class GeomOblik { // Ostala polja i metodi . . . public void nacrtaj() { . . // Naredbe za crtanje geometrijskog oblika . } }

Postavlja se pitanje kako napisati metod nacrtaj() u klasi GeomOblik? Problem je u tome to se svaki tip geometrijskih oblika crta na druga ciji na cin. To prakti cno zna ci da svaka klasa za trouglove, pravougaonike i krugove mora imati sopstveni metod nacrtaj() koji nadja cava verziju tog metoda u klasi GeomOblik:
public class Trougao extends GeomOblik { // Ostala polja i metodi . . . public void nacrtaj() { . . // Naredbe za crtanje trougla . } } public class Pravougaonik extends GeomOblik { // Ostala polja i metodi . . . public void nacrtaj() { . . // Naredbe za crtanje pravougaonika . } } public class Krug extends GeomOblik {

146

5. P OSEBNE KL ASE I INTERFEJSI

// Ostala polja i metodi . . . public void nacrtaj() { . . // Naredbe za crtanje kruga . } }

Ako je denisana promenljiva klasnog tipa GeomOblik, na primer,


GeomOblik oblik;

onda oblik u programu moe ukazivati, prema principu podtipa, na objekat bilo kojeg od podtipova Trougao, Pravougaonik ili Krug. Tokom izvravanja programa, vrednost promenljive oblik se moe menjati i ona moe c ak ukazivati na objekte ovih razli citih tipova u razli citom trenutku. Ali na osnovu principa polimorzma, kada se izvrava naredba poziva
oblik.nacrtaj();

onda se poziva metod nacrtaj() iz odgovaraju ce klase kojoj pripada objekat na koji trenutno ukazuje promenljiva oblik. Primetimo da se samo na osnovu teksta programa ne moe re ci koji c e se geometrijski oblik nacrtati prethodnom naredbom, jer to zavisi od tipa objekta na koga ukazuje promenljiva oblik u trenutku izvravanja te naredbe. Ali ono to je bitno je da mehanizam dinami ckog vezivanja metoda u Javi obezbeduje da se pozove pravi metod nacrtaj() i tako nacrta odgovaraju ci geometrijski oblik na ekranu. Ovo to je do sada re ceno ipak nije odgovorilo na nae polazno pitanje: kako napisati metod nacrtaj() u klasi GeomOblik, odnosno ta u njemu treba uraditi za crtanje opteg geometrijskog oblika? Odgovor je zapravo vrlo jednostavan: metod nacrtaj() u klasi GeomOblik ne treba da radi nita! Klasa GeomOblik predstavlja apstraktni pojam geometrijskog oblika i zato ne postoji na cin da se to neto nacrta. Samo speci cni, konkretni oblici kao to su trouglovi, pravougaonici i krugovi mogu da se nacrtaju. Ali ako metod nacrtaj() u klasi GeomOblik ne treba da radi nita, to onda otvara pitanje zato uopte moramo imati taj metod u klasi GeomOblik? Metod nacrtaj() ne moe se prosto izostaviti iz klase GeomOblik, jer onda za promenljivu oblik tipa GeomOblik naredba poziva
oblik.nacrtaj();

ne bi bila ispravna. To je zato to bi prilikom prevodenja programa Java pre vodilac proveravao da li klasa GeomOblik, koja je deklarisani tip promenljive

5.2. Apstraktne klase

147

oblik, sadri metod nacrtaj(). Ako to nije slu caj, prevodenje ne bi uspelo i

ne bi se dobila izvrna verzija programa. S druge strane, verzija metoda nacrtaj() iz klase GeomOblik se u programu ne ce nikad ni pozivati. Naime, ako bolje razmislimo, u programu ne postoji potreba da ikad konstruiemo stvarni objekat tipa GeomOblik. Ima rezona da deklariemo neku promenljivu tipa GeomOblik (kao to je to oblik), ali objekat na koga ona ukazuje c e uvek pripadati nekoj klasi koja nasleduje klasu GeomOblik. Zato se za klasu GeomOblik kae da je to apstraktna klasa. Apstraktna klasa je klasa koja se ne koristi za konstruisanje objekata, nego samo kao osnova za nasledivanje. Drugim re cima, apstraktna klasa slui samo za predstavljanje zajedni ckih svojstava svih njenih klasa naslednica. Za klasu koja nije apstraktna se kae da je konkretna klasa. U programu dakle moemo konstruisati samo objekte koji pripadaju konkretnim klasama, ali ne i apstraktnim. Promenljive c iji deklarisani tip predstavlja neka apstraktna klasa mogu zato ukazivati samo na objekte koji pripadaju konkretnim klasama naslednicama te apstraktne klase. Sli cno, za metod nacrtaj() u klasi GeomOblik kaemo da je apstraktni metod, jer on nije ni predviden cnog za pozivanje. U stvari, nema nita logi to bi on mogao prakti cno da uradi, jer stvarno crtanje obavljaju verzije tog metoda koje ga nadja cavaju u klasama koje nasleduju klasu GeomOblik. On se mora nalaziti u klasi GeomOblik samo da bi se na neki na cin obezbedilo da svi objekti nekog podtipa od GeomOblik imaju svoj metod nacrtaj(). Pored toga, uloga apstraktnog metoda nacrtaj() u klasi GeomOblik je da denie zajedni cki oblik zaglavlja svih stvarnih verzija tog metoda u konkretnim klasama koje nasleduju klasu GeomOblik. Ne postoji dakle nijedan razlog da apstraktni metod nacrtaj() u klasi GeomOblik sadri ikakve naredbe. Klasa GeomOblik i njen metod nacrtaj() su po svojoj prirodi dakle apstraktni entiteti. Ta c injenica se programski moe nazna citi dodavanjem modikatora abstract u zaglavlju denicije klase ili metoda. Dodatno, u slu caju apstraktnog metoda, telo takvog metoda kojeg c ini blok naredba izmedu vitic astih zagrada se ne navodi, nego se zamenjuje ta ckom-zapetom. Tako nova, proirena denicija klase GeomOblik kao apstraktne klase moe imati slede ci oblik:
public abstract class GeomOblik { private Color boja; public void oboji(Color novaBoja) { boja = novaBoja; nacrtaj();

148

5. P OSEBNE KL ASE I INTERFEJSI

} public abstract void nacrtaj(); // Ostala polja i metodi . . . } // apstraktni metod

U optem slu caju, klasa sa jednim apstraktnim metodom, ili vie njih, mora se i sama denisati da bude apstraktna. (Klasa se moe denisati da bude apstraktna c ak i ako nema nijedan apstraktni metod.) Pored apstraktnih metoda, apstraktna klasa moe imati konkretne metode i polja. Tako, u prethodnom primeru apstraktne klase GeomOblik, polje boja i metod oboji() su konkretni c lanovi te apstraktne klase. Klasa koja je u programu denisana da bude apstraktna ne moe se instancirati, odnosno nije dozvoljeno operatorom new konstruisati objekte te klase. Na primer, ako se u programu napie
new GeomOblik()

dobija se greka prilikom prevodenja programa. Iako se apstraktna klasa ne moe instancirati, ona moe imati konstruktore. Kao i kod konkretne klase, ukoliko nijedan konstruktor nije eksplicitno denisan u apstraktnoj klasi, automatski joj se dodaje podrazumevani konstruktor bez parametara. Ovo ima smisla, jer apstraktna klasa moe imati konkretna polja koja moda treba inicijalizovati posebnim vrednostima, a ne onim podrazumevanim. Naravno, konstruktore apstraktne klase nije mogu ce pozivati neposredno iza operatora new za konstruisanje objekata apstraktne klase, nego samo (direktno ili indirektno) koriste ci slubenu re c super u konstruktorima klasa koje nasleduju apstraktnu klasu. Mada se objekti apstraktne klase ne mogu konstruisati, naglasimo jo jednom da se mogu deklarisati objektne promenljive da budu tipa neke apstraktne klase. Takve promenljive medutim moraju ukazivati na objekte konkretnih klasa koje su naslednice apstraktne klase. Na primer:
GeomOblik oblik = new Pravougaonik();

Apstraktni metodi slue kao cuvari mesta za metode koji c e biti denisani u klasama naslednicama. Da bi neka klasa koja nasleduje apstraknu klasu bila konkretna, klasa-naslednica mora sadrati denicije konkretnih nadja canih verzija svih apstraktnih metoda nasledene klase. To zna ci da ukoliko se proiruje neka apstraktna klasa, onda postoje dve mogu cnosti. Prva je da se u klasi-naslednici ostavi da neki ili svi apstraktni metodi budu nedenisani. Onda je i nova klasa apstraktna i zato se mora navesti slubena re c abstract

5.3. Interfejsi

149

u zaglavlju njene denicije. Druga mogu cnost je da se u klasi-naslednici deniu svi nasledeni apstraktni metodi i onda se dobija nova konkretna klasa.

5.3 Interfejsi
Neki objektno orijentisani jezici (na primer, C++) dozvoljavaju da klasa moe direktno proirivati dve klase ili c ak vie njih. Ovo takozvano viestruko nasledivanje nije dozvoljeno u Javi, ve c neka klasa u Javi moe direktno proi rivati samo jednu klasu. Medutim, u Javi postoji koncept interfejsa koji obez beduje sli cne mogu cnosti kao viestruko nasledivanje, ali ne pove cava zna c ajno sloenost programskog jezika. Pre svega, otklonimo jednu terminoloku nepreciznost. Termin interfejs se ponekad koristi u vezi sa slikom jednog metoda kao crne kutije. Interfejs nekog metoda se sastoji od informacija koje je potrebno znati radi ispravnog pozivanja tog metoda u programu. To su ime metoda, tip njegovog rezultata i redosled i tip njegovih parametara. Svaki metod ima i implementaciju koja se sastoji od tela metoda u kojem se nalazi niz naredbi koje se izvravaju kada se metod pozove. Pored ovog zna cenja u vezi s metodima, termin interfejs u Javi ima i dodatno, potpuno razli cito koncepcijsko zna cenje za koje postoji slubena re c interface. Da ne bude zabune, naglasimo da se u ovoj knjizi podrazumeva ovo zna cenje kada se govori o interfejsu. Interfejs u ovom smislu se sastoji od skupa apstraktnih metoda (tj. interfejsa objektnih metoda), bez ikakve pridruene implementacije. (U stvari, Java interfejsi mogu imati i static final konstante, ali to nije njihova prvenstvena odlika.) Neka klasa implementira dati interfejs ukoliko sadri denicije svih (apstraktnih) metoda tog interfejsa. Interfejs u Javi nije dakle klasa, ve c skup mogu cnosti koje implementiraju ce klase moraju imati. Razmotrimo jedan jednostavan primer interfejsa u Javi. Metod sort() klase Arrays moe sortirati niz objekata, ali pod jednim uslovom: ti objekti moraju biti uporedivi. Tehni cki to zna ci da ti objekti moraju pripadati klasi koja implementira interfejs Comparable. Ovaj interfejs je u Javi denisan na slede ci na cin:
public interface Comparable { int compareTo(Object o); }

Kao to se vidi, denicija interfejsa u Javi je vrlo sli cna deniciji klase, osim to se umesto re ci class koristi re c interface i denicije svih metoda u interfejsu se izostavljaju. Svi metodi nekog interfejsa automatski postaju javni.

150

5. P OSEBNE KL ASE I INTERFEJSI

Zbog toga nije neophodno (ali nije ni greka) navesti specikator pristupa public u zaglavlju deklaracije nekog metoda u interfejsu. U speci cnom primeru interfejsa Comparable, neka klasa koja implementira taj interfejs mora denisati metod compareTo(), a taj metod mora imati parametar tipa Object i vratiti rezultat tipa int. Naravno, ovde postoji dodatni semanti cki uslov koji se implicitno podrazumeva, ali se negova ispunjenost ne moe obezbediti u interfejsu: u izrazu x.compareTo(y) metod compareTo() mora zaista uporediti dva objekta x i y daju ci rezultat koji ukazuje koji od njih je ve ci. Preciznije, ovaj metod treba da vrati negativan broj ako je x manje od y, nulu ako su jednaki i pozitivan broj ako je x ve ce od y. Uz denicije svih metoda interfejsa, svaka klasa koja implementira neki interfejs mora sadrati i eksplicitnu deklaraciju da ona implementira dati interfejs. To se postie pisanjem slubene re ci implements i imena tog interfejsa iza imena klase koja se denie. Na primer:
public class Radnik implements Comparable { String ime; // ime i prezime radnika double plata; // plata radnika . . // Ostala polja i metodi . public int compareTo(Object o) { Radnik drugiRadnik = (Radnik) o; if (this.plata < drugiRadnik.plata) return -1; if (this.plata > drugiRadnik.plata) return +1; return 0; } }

Obratite panju na to da u metodu compareTo() klase Radnik, radnici se uporeduju na osnovu njihove plate. (Ako objekti nisu jednaki, pozitivne ili ne gativne vrednosti koje su rezultat metoda compareTo() nisu bitni.) Isto tako, za ovaj konkretan metod se mora navesti specikator pristupa public, iako to nije bilo obavezno za njegovu apstraktnu deklaraciju u interfejsu Comparable. Pretpostavimo da u programu konkretna klasa Radnik koja implementira interfejs Comparable slui za predstavljanje pojedinih radnika neke rme. Ako dodatno pretpostavimo da su svi radnici predstavljeni nizom baznog tipa Radnik, onda se ovi radnici mogu jednostavno sortirati po rastu cem redosledu njihovih plata:
Radnik[] spisakRadnika = new Radnik[500];

5.3. Interfejsi

151

. . . Arrays.sort(spisakRadnika);

Napomenimo jo da se u vezi sa interfejsima c esto koristi jedan kra ci terminoloki izraz. Naime, kae se da neki objekat implementira interfejs, iako se zapravo misli da taj objekat pripada klasi koja implementira dati interfejs. Tako, u prethodnom primeru moemo re ci da svaki objekat tipa Radnik implementira interfejs Comparable.

Osobine interfejsa
Po svojoj glavnoj nameni, interfejsi sadre jedan ili vie apstraktnih metoda (ali mogu imati i konstante). Interfejsi se sli cno klasama piu u datotekama sa ekstenzijom .java, a prevedena verzija (bajtkod) interfejsa se nalazi u datoteci sa ekstenzijom .class. Ono to je isto tako vano naglasiti je ta interfejsi ne mogu imati. Interfejsi ne mogu imati objektna polja, niti metodi interfejsa mogu imati implementaciju. Dodavanje polja i implementacije metoda je zadatak klasa koje implementiraju interfejs. Prema tome, interfejsi su sli cni apstraktnim klasama bez objektnih polja. Iako su sli cni (apstraktnim) klasama, interfejsi nisu klase. To zna ci da se interfejsi ne mogu koristiti za konstruisanje objekata. Pisanjem, na primer,
new Comparable()

izaziva se greka u programu. Denicijom nekog interfejsa u Javi se tehni cki uvodi novi klasni tip podataka. Vrednosti tog klasnog tipa su objekti klasa koje implementiraju denisani interfejs. To zna ci da ime interfejsa moemo koristiti za tip promenljive u naredbi deklaracije, kao i za tip formalnog parametra i za tip rezulatata u deniciji nekog metoda. Prema tome, u Javi tip moe biti klasa, interfejs ili jedan od osam primitivnih tipova. To su jedine mogu cnosti, ali samo klase slue za konstruisanje novih objekata. U programu se dakle mogu deklarisati objektne promenljive c iji tip predstavlja neki interfejs, na primer:
Comparable x; // OK

Poto se interfejsi implementiraju, a ne nasleduju kao apstraktne klase, in terfejsna promenljiva (tj. promenljiva c iji je tip neki interfejs) moe ukazivati samo na objekte onih klasa koje implementiraju dati interfejs. Tako, za prethodni primer klase Radnik koja implementira interfejs Comparable moemo pisati:

152

5. P OSEBNE KL ASE I INTERFEJSI

Comparable x = new Radnik();

// OK

Dalje, poznato je da se operator instanceof moe koristiti za proveravanje da li neki objekat pripada speci cnoj klasi. Taj operator se moe koristiti i za proveravanje da li neki objekat implementira dati interfejs:
if (nekiObjekat instanceof Comparable) . . .

Kao to se mogu praviti hijerarhije klasa, tako se mogu proirivati i interfejsi. Interfejs koji proiruje drugi interfejs nasleduje sve njegove deklarisane metode. Hijerarhije interfejsa su nezavisne od hijerarhija klasa, ali imaju istu ulogu: to je na cin da se od nekog najoptijeg entiteta na vrhu hijerarhije izrazi ve ci stepen specijalizacije kako se prelazi na donje nivoe hijerarhije. Na primer:
public interface Radio { void uklju ci(); void isklju ci(); } public interface Klasi canRadio extends Radio { void izaberiStanicu(); } public interface InternetRadio extends Radio { void izaberiSajt(); }

Za razliku od klase koja moe direktno proiriti samo jednu klasu, interfejs moe direktno proirivati vie interfejsa. U tom slu caju se iza re ci extends navode svi nazivi interfejsa koji se proiruju, razdvojeni zapetama. Mada interfejsi ne mogu imati objektna polja i stati cke metode, u njima se mogu nalaziti konstante. Na primer:
public interface Klasi cniRadio extends Radio { double B92 = 92.5; double STUDIO_B = 99.1; void izaberiStanicu(); }

Sli cno kao to se za metode interfejsa podrazumeva da su public, polja interfejsa automatski postaju public static final konstante. U uvodnom delu o interfejsima smo spomenuli da u Javi nije omogu ceno viestruko nasledivanje klasa. Ali to se moe nadomestiti time to klase mogu istovremeno implementirati vie interfejsa. U deklaraciji jedne takve klase se nazivi interfejsa koji se implementiraju navode razdvojeni zapetama. Na

5.3. Interfejsi

153

primer, u Javi je denisan interfejs Cloneable tako da, po konvenciji, klase koje implementiraju ovaj interfejs treba da nadja caju metod clone() klase Object. Zato ukoliko elimo da se objekti neke klase mogu, recimo, klonirati i uporedivati, u deniciji klase tih objekata treba navesti da se implementiraju interfejsi Cloneable i Comparable. Na primer:
public class Radnik implements Cloneable, Comparable { ... }

Naravno, telo ove klase Radnik sada mora sadrati denicije metoda clone() i compareTo().

Interfejsi i apstraktne klase


Paljiviji c itaoci se verovatno pitaju zato su potrebni interfejsi kada se oni naizgled mogu potpuno zameniti apstraktnim klasama. Zato recimo interfejs Comparable ne bi mogao biti apstraktna klasa? Na primer:
abstract class Comparable { // Zato ne? public abstract int compareTo(Object o); }

Onda bi klasa Radnik mogla prosto da nasledi ovu apstraktnu klasu i da denie metod compareTo():
public class Radnik extends Comparable { // Zato ne? String ime; // ime i prezime radnika double plata; // plata radnika . . // Ostala polja i metodi . public int compareTo(Object o) { Radnik drugiRadnik = (Radnik) o; if (this.plata < drugiRadnik.plata) return -1; if (this.plata > drugiRadnik.plata) return +1; return 0; } }

Glavni problem kod kori cenja apstraktnih klasa za izraavanje apstraktnih mogu cnosti objekata je to to neka klasa u Javi moe direktno naslediti samo jednu klasu. Ako pretpostavimo da klasa Radnik ve c nasleduje jednu klasu, recimo Osoba, onda klasa Radnik ne moe naslediti i drugu:
public class Radnik extends Osoba, Comparable // GREKA

154

5. P OSEBNE KL ASE I INTERFEJSI

Ali neka klasa u Javi moe implementirati vie interfejsa (s nasledivanjem jedne klase ili bez toga):
public class Radnik extends Osoba implements Comparable // OK

5.4 Ugnjedene klase


Do sada smo nau cili da se u Javi neke programske konstrukcije mogu ugnjedavati, na primer upravlja cke naredbe, dok se druge ne mogu ugnjeda vati, na primer metodi. Postavlja se sada pitanje da li se klase, kao najsloeniji vid programske celine, mogu ugnjedavati i da li to ima smisla? Odgovor na oba pitanja je, moda malo iznenaduju ce, potvrdan. Ugnjedena klasa je klasa koja je denisana unutar druge klase. Ovakav pristup u dizajnu programa je koristan uglavnom iz tri razloga: 1. Kada je neka klasa pomo cna za glavnu klasu i ne koristi se van te ve ce klase, nema razloga da pomo cna klasa bude izdvojena kao samostalna klasa. 2. Kada neka klasa slui za konstruisanje samo jednog objekta te klase, prirodnije je takvu klasu (na kra ci na cin) denisati ba na mestu konstruisanja njenog jedinstvenog objekta. 3. Metodi ugnjedene klase mogu koristiti sva polja, c ak i privatna, obu hvataju ce klase. Tema ugnjedenih klasa je prili cno sloena i svi njeni detalji prevazilaze okvire ove knjige. Zato c emo ovde pomenuti samo one mogu cnosti ugnjeda vanja klasa u Javi koje su dovoljne za razumevanje jednostavnijih programskih tehnika.2 Primena ugnjedenih klasa je naro cito korisna kod gra ckog pro gramiranja o c emu se govori u poglavlju 6. U tom poglavlju c emo upoznati i prakti cne primere u kojima se naj ce ce koriste ugnjedene klase. Pre svega, neka klasa se moe denisati unutar druge klase na isti na cin na koji se deniu uobi cajeni c lanovi spoljanje klase: polja i metodi. To zna ci da se ugnjedena klasa moe deklarisati sa ili bez modikatora static , pa se prema tome razlikuju stati cke i objektne ugnjedene klase:
class SpoljanjaKlasa { . . // Ostala polja i metodi
2 U stvari, pravilnije je moda govoriti o ugnjedavanju tipova, a ne klasa, jer se mogu denisati i interfejsi unutar klasa. Tu dodatnu komplikaciju ne cemo razmatrati u knjizi.

5.4. Ugnjedene klase

155

. static class Stati ckaUgnje denaKlasa { . . . } class ObjektnaUgnje denaKlasa { . . . } }

Stati cke i objektne ugnjedene klase se smatraju obi cnim c lanovima obu hvataju ce klase, to zna ci da se za njih mogu koristiti specikatori pristupa public, private i protected, ili nijedan od njih. (Podsetimo se da se obi cne klase mogu denisati samo sa specikatorom pristupa public ili bez njega.) Druga mogu cnost kod ugnjedavanja klasa je denisanje jedne klase unu tar nekog metoda (ta cnije, blok-naredbe) druge klase. Takve ugnjedene klase mogu biti lokalne ili anonimne. Ove ugnjedene klase imaju koncepcijski sli cne osobine kao lokalne promenljive metoda.3

Stati cke ugnjedene klase


Denicija stati cke ugnjedene klase ima isti oblik kao denicija obi cne klase, osim to se nalazi unutar druge klase i sadri modikator static.4 Stati cka ugnjedena klasa je deo stati cke strukture obuhvataju ce klase, logi cki potpuno isto kao stati cka polja i metodi te klase. Kao i stati cki metod, na primer, stati cka ugnjedena klasa nije vezana za objekte obuhvataju ce klase i postoji nezavisno od njih. U metodima obuhvataju ce klase se stati cka ugnjedena klasa moe ko ristiti za konstruisanje objekata na uobi cajeni na cin. Ako nije deklarisana kao privatna, stati cka ugnjedena klasa se takode moe koristiti izvan obuhvataju ce klase, ali se onda ta cka-notacijom mora navesti njeno c lanstvo u obuhvataju coj klasi. Na primer, pretpostavimo da klasa KoordSistem predstavlja koordinatni sistem u dvodimenzionalnoj ravni. Pretpostavimo jo da klasa KoordSistem sadri stati cku ugnjedenu klasu Du koja predstavlja jednu du izmedu dve ta cke. Na primer:
public class KoordSistem {
3 Nestati cke ugnjedene klase (tj. objektne, lokalne i anonimne) c esto se nazivaju unutra

nje klase. 4 Nabrojivi tip denisan unutar neke klase smatra se stati ckom ugnjedenom klasom, iako se u njegovoj deniciji ne navodi modikator static.

156

5. P OSEBNE KL ASE I INTERFEJSI

. . // Ostali clanovi klase KoordSistem . public static class Du { // Predstavlja du u ravni od // ta cke (x1,y1) do ta cke (x2,y2) double x1, y1; double x2, y2; } }

Objekat klase Du bi se unutar nekog metoda klase KoordSistem konstruisao izrazom new Du(). Izvan klase KoordSistem bi se morao koristiti izraz new KoordSistem.Du(). Obratite panju na to da kada se prevede klasa KoordSistem, dobijaju se zapravo dve datoteke bajtkoda. Iako se denicija klase Du nalazi unutar klase KoordSistem, njen bajtkod se smeta u posebnu datoteku c ije je ime KoordSistem$Du.class. Bajtkod klase KoordSistem se smeta, kao to je to uobi cajeno, u datoteku KoordSistem.class. Stati cka ugnjedena klasa je vrlo sli cna regularnoj klasi na najviem nivou. Jedna od razlika je to to se njeno ime hijerarhijski nalazi unutar prostora imena obuhvataju ce klase, sli cno kao to se ime regularne klase nalazi unutar prostora imena odgovaraju ceg paketa. Pored toga, stati cka ugnjedena klasa ima potpun pristup stati ckim c lanovima obuhvataju ce klase, c ak i ako su oni deklarisani kao privatni. Obrnuto je takode cno: obuhvataju ca klasa ta ima potpun pristup svim c lanovima ugnjedene klase. To moe biti jedan od razloga za denisanje stati cke ugnjedene klase, jer tako jedna klasa moe dobiti pristup privatnim c lanovima druge klase a da se pri tome ti c lanovi ne otkrivaju drugim klasama.

Objektne ugnjedene klase


Objektna ugnjedena klasa je klasa koja je denisana kao c lan obuhva taju ce klase bez slubene re ci static. Ako je stati cka ugnjedena klasa ana logna stati ckom polju ili stati ckom metodu, onda je objektna ugnjedena kla sa analogna objektnom polju ili objektnom metodu. Zbog toga je objektna ugnjedena klasa vezana zapravo za objekat obuhvataju ce klase. Nestati cki c lanovi neke obi cne klase odreduju ta c e se nalaziti u svakom konstruisanom objektu te klase. To vai i za objektne ugnjedene klase, bar logi cki, odnosno moe se smatrati da svaki objekat obuhvataju ce klase ima sopstveni primerak objektne ugnjedene klase. Ovaj primerak ima pristup

5.4. Ugnjedene klase

157

do svih posebnih primeraka polja i metoda nekog objekta, c ak i onih koji su deklarisani kao privatni. Dva primerka objektne ugnjedene klase u razli citim objektima se razlikuju, jer koriste razli cite primerke polja i metoda u razli citim objektima. U stvari, pravilo na osnovu kojeg se moe odlu citi da li ugnjedena klasa treba da bude stati cka ili objektna je jednostavno: ako ugnjedena klasa mora koristiti neko objektno polje ili objektni metod obuhvataju ce klase, ona i sama mora biti objektna. Objektna ugnjedena klasa se izvan obuhvataju ce klase mora koristiti uz konstruisani objekat obuhvataju ce klase u formatu:
promenljiva.ObjektnaUgnje denaKlasa

U ovom zapisu, promenljiva ozna cava ime promenljive koja ukazuje na objekat obuhvataju ce klase. Ova mogu cnost se u praksi ipak retko koristi, jer se objektna ugnjedena klasa obi cno koristi samo unutar obuhvataju ce klase, a onda je dovoljno navesti njeno prosto ime. Da bi se konstruisao objekat koji pripada nekoj objektnoj ugnjedenoj klasi, mora se najpre imati objekat njene obuhvataju ce klase. Unutar obuhvataju ce klase se konstruie objekat koji pripada njenoj objektnoj ugnjede noj klasi za objekat na koga ukazuje this. Objekat koji pripada objektnoj ugnjedenoj klasi se permanentno vezuje za objekat obuhvataju ce klase i ima potpun pristup svim c lanovima tog objekta. Pokuajmo da ovo razjasnimo na jednom primeru. Posmatrajmo klasu koja predstavlja igru tabli ca s kartama. Ova klasa moe imati objektnu ugnjedenu klasu koja predstavlja igra ce tabli ca:
public class Tabli c { private Karta[] pil; ca // pil karata za this igru tabli

private class Igra c { // jedan igra c this igre tabli ca . . . } . . // Ostali clanovi klase Tabli c . }

Ako je igra promenljiva tipa Tabli c, onda objekat igre tabli ca na koga
igra ukazuje koncepcijski sadri svoj primerak objektne ugnjedene klase Igra c. U svakom objektnom metodu klase Tabli c, novi igra c bi se na uo-

bi cajeni na cin konstruisao izrazom new Igra c(). (Ali izvan klase Tabli c bi se novi igra c konstruisao izrazom igra.new Igra c().) Unutar klase Igra c,

158

5. P OSEBNE KL ASE I INTERFEJSI

svi objektni metodi imaju pristup objektnom polju pil obuhvataju ce klase Tabli c. Jedna igra tabli ca koristi svoj pil karata i ima svoje igra ce; igra ci neke druge igra tabli ca koriste drugi il karata. Ovaj prirodan efekat je upravo postignut time to je klasa Igra c denisana da nije stati cka. Objekat klase Igra c u prethodnom primeru predstavlja igra ca jedne konkretne igre tabli ca. Da je klasa Igra c bila denisana kao stati cka ugnjedena klasa, onda bi ona predstavljala igra ca tabli ca u optem smislu, nezavisno od konkretne igre tabli ca.

Lokalne klase
Lokalna klasa je ona koja je denisana unutar nekog metoda druge klase.5 Poto se svi metodi moraju nalaziti unutar neke klase, lokalne klase moraju biti ugnjedene unutar obuhvataju ce klase. Zbog toga lokalne klase imaju sli cne osobine kao objektne ugnjedene klase, mada predstavljaju potpuno razli citu vrstu ugnjedenih klasa. Lokalne klase u odnosu na objektne ugnje dene klase stoje otprilike kao lokalne promenljive u odnosu na objektna po lja klase. Kao i neka lokalna promenljiva, lokalna klasa je vidljiva samo unutar metoda u kojem je denisana. Na primer, ako se neka objektna ugnjedena klasa koristi samo unutar jednog metoda svoje obuhvataju ce klase, obi cno ne postoji razlog zbog kojeg se ona ne moe denisati unutar tog metoda, a ne kao c lanica obuhvataju ce klase. Na taj na cin se denicija klase pomera jo blie mestu gde se koristi, to skoro uvek doprinosi boljoj c itljivosti programskog koda. Neke od zna cajnih osobina lokalnih klasa su: Sli cno objektnim ugnjedenim klasama, lokalne klase su vezane za obu hvataju ci objekat i imaju pristup svim njegovim c lanovima, c ak i privatnim. Prema tome, objekti lokalne klase su pridrueni jednom objektu obuhvataju ce klase c ija se referenca u metodima lokalne klase moe koristiti putem promenljive sloenog imena Obuhvataju caKlasa.this. Sli cno pravilu za oblast vaenja lokalne promenljive, lokalna klasa vai samo unutar metoda u kojem je denisana i njeno ime se nikad ne moe koristiti van tog metoda. Obratite ipak panju na to da objekti lokalne klase konstruisani u njenom obuhvataju cem metodu mogu postojati i nakon zavretka izvravanja tog metoda. Sli cno lokalnim promenljivim, lokalne klase se ne mogu denisati sa modikatorima public, private, protected ili static. Ovi modi5 Ta cnije, lokalna klasa se moe denisati unutar svakog bloka Java koda.

5.4. Ugnjedene klase

159

katori mogu stajati samo uz c lanove klase i nisu dozvoljeni za lokalne promenljive ili lokalne klase. Pored c lanova obuhvataju ce klase, lokalna klasa moe koristiti lokalne promenljive i parametre metoda pod uslovom da su oni deklarisani sa modikatorom final. Ovo ograni cenje je posledica toga to ivotni vek nekog objekta lokalne klase moe biti mnogo dui od ivotnog veka lokalne promenljive ili parametra metoda u kojem je lokalna klasa denisana. (Podsetimo se da je ivotni vek lokalne promenljive ili parametra nekog metoda jednak vremenu izvravanja tog metoda.) Zbog toga lokalna klasa mora imati svoje privatne primerke svih lokalnih promenljivih ili parametara koje koristi (to se automatski obezbeduje od strane Java prevodioca). Jedini na cin da se obezbedi da lokalne promenljive ili parametri metoda budu isti kao privatni primerci lokalne klase jeste da se zahteva da oni budu final.

Anonimne klase
Anonimne klase su lokalne klase bez imena. One se uglavnom koriste u slu cajevima kada je potrebno konstruisati samo jedan objekat neke klase. Umesto posebnog denisanja te klase i zatim konstruisanja njenog objekta samo na jednom mestu, oba efekta se istovremeno mogu posti ci pisanjem operatora new u jednom od dva specijalna oblika:
new natklasa (lista-argmenata) { metodi-i-promenljive }

ili
new interfejs () { metodi-i-konstante }

Ovi oblici operatora new se mogu koristiti na svakom mestu gde se moe navesti obi can operator new. Obratite panju na to da dok se denicija lokalne klase smatra naredbom nekog metoda, denicija anonimne klase uz operator new je formalno izraz. To zna ci da kombinovani oblik operatora new moe biti u sastavu ve ceg izraza gde to ima smisla. Oba prethodna izraza slue za denisanje bezimene nove klase c ije se telo nalazi izmedu castih zagrada i istovremeno se konstruie objekat koji viti pripada toj klasi. Preciznije, prvim izrazom se proiruje natklasa tako to se dodaju navedeni metodi-i-promenljive. Argumenti navedeni izmedu zagrada se prenose konstruktoru natklase koja se proiruje. U drugom izrazu

160

5. P OSEBNE KL ASE I INTERFEJSI

se podrazumeva da anonimna klasa implementira interfejs denisanjem svih njegovih deklarisanih metoda i da proiruje klasu Object. U svakom slu caju, glavni efekat koji se postie je konstruisanje posebno prilagodenog objekta, ba na mestu u programu gde je i potreban. Anonimne klase se c esto koriste za postupanje sa dogadajima koji nastaju pri radu programa koji koriste gra cki korisni cki interfejs. O detaljima rada sa elementima gra ckog korisni ckog interfejsa bi ce vie re ci u poglavlju 6 koje je posve ceno gra ckom programiranju. Sada c emo samo radi ilustracije, bez ulaenja u sve nese, pokazati programski fragment u kojem se konstruie jednostavna komponenta gra ckog korisni ckog interfejsa i deniu metodi koji se pozivaju kao reakcija na dogadaje koje proizvodi ta komponenta. Taj gra cki element je dugme, ozna ceno prigodnim tekstom, koje proizvodi razne vrste dogadaja zavisno od korisni cke aktivnosti s miom nad njim. U primeru u nastavku, najpre se konstruie gra cki element dugme koje je objekat standardne klase Button iz paketa java.awt. Nakon toga se za novokonstruisano dugme poziva metod addMouseListener() kojim se registruje objekat koji moe da reaguje na dogadaje izazvane klikom mia na dugme, prelaskom strelice mia preko dugmeta i tako dalje. Tehni cki to zna ci da argument poziva metoda addMouseListener() mora biti objekat koji pripada klasi koja implementira interfejs MouseListener. Bez moda potpunog razumevanja svih ovih detalja o kojima se vie govori u poglavlju 6, ovde je za sada vano primetiti samo to da nam treba jedan jedini objekat posebne klase koju zato moemo denisati da bude anonimna klasa. Pored toga, specijalni oblik izraza sa operatorom new za konstruisanje tog objekta moemo pisati u samom pozivu metoda addMouseListener() kao njegov argument:
Button dugme = new Button("Pritisni me"); // dugme sa tekstom dugme.addMouseListener(new MouseListener() { // po cetak tela // anonimne klase // Svi metodi interfejsa MouseListener se moraju definisati public void mouseClicked(MouseEvent e) { System.out.println("Kliknuto na dugme"); } public void mousePressed(MouseEvent e) { } public void mouseReleased(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } }); // srednja zagrada i ta cka-zapeta zavravaju poziv metoda!

Budu ci da anonimne klase nemaju ime, nije mogu ce denisati konstruktore za njih. Anonimne klase se obi cno koriste za proirivanje jednostavnih klasa c iji konstruktori nemaju parametre. Zato su zagrade u deniciji ano-

5.4. Ugnjedene klase

161

nimne klase c esto prazne, kako u slu caju proirivanja klasa tako i u slu caju implementiranja interfejsa. Sve anonimne klase obuhvataju ce klase prilikom prevodenja daju poseb ne datoteke u kojima se nalazi njihov bajtkod. Na primer, ako je NekaKlasa ime obuhvataju ce klase, onda datoteke sa bajtkodom njenih anonimnih klasa imaju imena NekaKlasa$1.class, NekaKlasa$2.class i tako dalje.

G LAVA 6

G RAFI CKO PROGRAMIRANJE

Do sada smo upoznali konzolne Java programe koji ulazne podatke u citavaju preko tastature i za izlazne podatke koriste tekstualni prikaz na ekranu. Medutim, svi moderni ra cunari rade pod operativnim sistemima koji se za snivaju na gra ckim prozorima. Zbog toga su dananji korisnici ra cunara naviknuti da sa programima komuniciraju preko gra ckog korisni ckog interfejsa (engl. graphical user interface, skra ceno GUI). Gra cki korisni cki interfejs se odnosi na vizuelni deo programa koji korisnik vidi na ekranu i preko koga moe upravljati programom tokom njegovog izvravanja. Programski jezik Java omogu cava naravno pisanje ovakvih gra ckih programa. U stvari, jedan od osnovnih ciljeva Jave od samog po cetka je bio da se obezbedi relativno lako pisanje gra ckih programa. Ovaj naglasak na gra ckim programima je zapravo razlog zato su zanemareni konzolni programi i, recimo, na cin u citavanja podataka u njima nije tako jednostavan. U ovom poglavlju c emo se upoznati sa osnovnim principima pisanja gra ckih programa u Javi.

6.1 Gra cki programi


Gra cki programi se znatno razlikuju od konzolnih programa. Najve ca razlika je u tome to se konzolni program izvrava sinhrono (od po cetka do kraja, jedna naredba iza druge). U konzolnim programima se zato programski odreduje trenutak kada korisnik treba da unese ulazne podatke i kada se prikazuju izlazni podaci. Nasuprot tome, kod gra ckih programa je korisnik taj koji odreduje kada ho ce da unese ulazne podatke i naj ce ce on sm odreduje kada se prikazuju izlazni podaci programa. Za gra cke programe se zato kae da su vodeni dogadajima (engl. event-driven programs). To zna ci da korisnikove aktivnosti, poput pritiska tasterom mia na neki gra cki element ekrana ili pritiska na taster tastature, generiu dogadaje na koje program mora

164

6. G RAFI CKO PROGRAMIRANJE

reagovati na odgovaraju ci na cin. Pri tome naravno, ovaj model rada programa u Javi je prilagoden cinu programiranja: dogadaji objektno orijentisanom na su objekti, boje i fontovi su objekti, gra cke komponente su objekti, a na pojavu nekog dogadaja se pozivaju objektni metodi neke klase koji propisuju reakciju programa na taj dogadaj. Gra cki programi imaju mnogo bogatiji korisni cki interfejs nego konzolni programi. Taj interfejs se moe sastojati od gra ckih komponenti kao to su prozori, meniji, dugmad, polja za unos teksta, trake za pomeranje teksta i tako dalje. (Ove komponente se u Windows okruenju nazivaju i kontrole.) Gra cki programi u Javi svoj korisni cki interfejs grade programski, naj ce ce u toku inicijalizacije glavnog prozora programa. To obi cno zna ci da glavni metod Java programa konstruie jednu ili vie od ovih komponenti i prikazuje ih na ekranu. Nakon konstruisanja neke komponente, ona sledi svoju sopstvenu logiku kako se crta na ekranu i kako reaguje na dogadaje izazvane nekom korisnikovom akcijom nad njom. Pored obi cnih, pravih ulaznih podataka, gra cki programi imaju jo jedan njihov oblik dogadaje. Dogadaje proizvode razne korisnikove aktivno sti s zi ckim uredajima mia i tastature nad gra ckim elementima programa. Tu spadaju na primer izbor iz menija prozora na ekranu, pritisak tasterom mia na dugme, pomeranje mia, pritisak nekog tastera mia ili tastature i sli cno. Sve ove aktivnosti generiu dogadaje koji se prosleduju gra ckom pro gramu. Program prima dogadaje kao ulaz i obraduje ih u delu koji se naziva rukovalac dogadaja (engl. event handler ili event listener ). Zbog toga se c esto kae da je logika gra ckih programa zasnovana na dogadajima korisni ckog interfejsa. Glavna podrka za pisanje gra ckih programa u Javi se sastoji od dve standardne biblioteke klasa. Starija biblioteka se naziva AWT (skra ceno od engl. Abstract Window Toolkit ) i njene klase se nalaze u paketu java.awt. Karakteristika ove biblioteke koja postoji od prve verzije programskog jezika Java jeste to to obezbeduje upotrebu minimalnog skupa komponenti gra ckog interfejsa koje poseduju sve platforme koje podravaju Javu. Klase iz ove biblioteke se oslanjaju na gra cke mogu cnosti konkretnog operativnog sistema, pa su AWT komponente morale da imaju najmanji zajedni cki imenitelj mogu cnosti koje postoje na svim platformama. Zbog toga se za njih c esto kae da izgledaju podjednako osrednje na svim platformama. To je bio glavni razlog zato su od verzije Java 1.2 mnoge klase iz biblioteke AWT ponovo napisane tako da ne zavise od konkretnog operativnog sistema. Nova biblioteka za gra cke programe u Javi je nazvana Swing i njene klase se nalaze u paketu javax.swing. Swing komponenete su potpuno napisane u Javi i zato podjednako izgledaju na svim platformama. Moda jo vanije, te

6.1. Gra cki programi

165

komponente rade na isti na cin nezavisno od operativnog sistema, tako da su otklonjeni i svi problemi u vezi sa razli citim suptilnim grekama AWT komponenti prilikom izvavanja na razli citim platformama. Obratite panju na to da biblioteka Swing nije potpuna zamena za biblioteku AWT, nego se nadovezuje na nju. Iz biblioteke Swing se dobijaju gra cke komponente s ve cim mogu cnostima, ali se iz biblioteke AWT nasleduje osnovna arhitektura kao to je, recimo, mehanizam rukovanja dogadajima. U stvari, kompletno gra cko programiranje u Javi se bazira na biblioteci JFC (skra ceno od engl. Java Foundation Classes), koja obuhvata Swing/AWT i sadri jo mnogo vie od toga. Gra cki programi u Javi se danas uglavnom zasnivaju na biblioteci Swing iz vie razloga: Biblioteka Swing sadri bogatu kolekciju GUI komponenti kojima se lako manipulie u programima. Biblioteka Swing se minimalno oslanja na bazni operativni sistem ra cunara. Biblioteka Swing prua korisniku konzistentan utisak i na cin rada nezavisno od platforme na kojoj se program izvrava. Mnoge komponente u biblioteci Swing imaju svoje stare verzije u biblioteci AWT, ali imena odgovaraju cih klasa su paljivo birana kako ne bi dolazilo do njihovog sukoba u programima. (Imena klasa u biblioteci Swing obi cno po cinju velikim slovom J, na primer JButton.) Zbog toga je bezbedno u Java programima uvoziti oba paketa java.awt i javax.swing, to se obi cno i radi, jer je c esto sa novim komponentama iz biblioteke Swing potrebno koristiti i osnovne mehanizme rada s njima iz biblioteke AWT.

Primer: gra cki ulaz/izlaz programa


Da bismo pokazali kako se lako moe realizovati gra cki ulaz i izlaz programa u Javi, u nastavku je prikazan mali program u kojem se od korisnika najpre zahteva nekoliko ulaznih podataka, a zatim se na ekranu ispisuje poruka dobrodolice za korisnika. Pri tome, za ove ulazne i izlazne podatke programa koriste se gra cki dijalozi. Ovaj program nema svoj glavni prozor radi jednostavnosti, nego samo koristi komponentu okvira za dijalog kako bi se korisniku omogu cilo da unese ulazne podatke, kao i to da se u gra ckom okruenju prikau izlazni podaci. Za gra cke dijaloge se koristi standardna klasa JOptionPane i dva njena stati cka metoda showInputDialog() i showMessageDialog(). Oba metoda imaju nekoliko preoptere cenih verzija, a u programu se koristi ona koja u oba

166

6. G RAFI CKO PROGRAMIRANJE

slu caja sadri c etiri parametra: prozor kome dijalog pripada, poruka koja se prikazuje u dijalogu, naslov dijaloga i vrsta dijaloga. Kako ovaj jednostavan program nema glavni prozor, prvi argument ovih metoda u svim slu cajevima je null, drugi i tre ci argumenti su stringovi odgovaraju ceg teksta, dok c etvrti argument predstavlja konstantu izabranu od onih koje su takode denisane u klasi JOptionPane.
import javax.swing.*; public class ZdravoGUI { public static void main(String[] args) { String ime = JOptionPane.showInputDialog(null, "Kako se zovete?", "Grafi cki ulaz", JOptionPane.QUESTION_MESSAGE); String godine = JOptionPane.showInputDialog(null, "Koliko imate godina?", "Grafi cki ulaz", JOptionPane.QUESTION_MESSAGE); int god = Integer.parseInt(godine); String poruka = "Zdravo " + ime + "!\n"; poruka += god + " su najlepe godine."; JOptionPane.showMessageDialog(null, poruka, "Grafi cki izlaz", JOptionPane.INFORMATION_MESSAGE); System.exit(0); } }

Vra cena vrednost metoda showInputDialog() jeste string koji je korisnik uneo u polju prikazanog gra ckog dijaloga. Primetimo da je za godine korisnika potrebno taj string konvertovati u celobrojnu vrednost primenom odgovaraju ceg metoda parseInt() omota cke klase Integer. Izvravanjem programa se dobijaju tri okvira za dijalog koja su prikazana na slici 6.1.

6.2 Gra cki elementi


Gra cki elementi u Javi koji se mogu koristiti za pravljenje korisni ckog interfejsa programa pripadaju trima glavnim kategorijama:

6.2. Gra cki elementi

167

Slika 6.1: Tri okvira za dijalog gra ckog programa ZdravoGUI.

Prozori. To su jednostavne provaougaone oblasti koje se mogu nacrtati na ekranu. Prozori predstavljaju kontejnere najvieg nivoa i dalje se dele u dve vrste: Okviri. To su permanenti prozori koji mogi imati meni sa opcijama. Okviri su predvideni da ostaju na ekranu za sve vreme izvravanja programa. Dijalozi. To su privremeni prozori koji se mogu skloniti sa ekrana tokom izvravanja programa. Komponente. To su vizuelni elementi koji imaju veli cinu i poziciju na ekranu i koji mogu proizvoditi dogadaje. Kontejneri. To su komponente koje mogu obuhvatati druge komponente. Prozor najvieg nivoa, odnosno prozor koji se ne nalazi unutar drugog prozora, u Javi se naziva okvir (engl. frame). Okvir je dakle nezavisan prozor koji moe posluiti kao glavni prozor programa. Okvir poseduje nekoliko ugradenih mogu cnosti: moe se otvoriti i zatvoriti, moe mu se promeniti ve li cina i, najvanije, moe sadrati druge GUI komponente kao to su dugmad i meniji. Svaki okvir na vrhu obavezno sadri dugme sa ikonom, polje za naslov i tri manipulativna dugmeta za minimizovanje, maksimizovanje i zatvaranje okvira. U biblioteci Swing se nalazi klasa JFrame za konstruisanje i podeavanje okvira koji ima pomenute standardne mogu cnosti. Ono to okvir tipa JFrame nema su druge komponente koje c ine njegov sadraj unutar okvira. One se moraju programski dodati nakon konstruisanja okvira. Rezultat jednostavnog programa u listingu 6.1 je prazan okvir na ekranu c iji izgled je prikazan na slici 6.2.

168

6. G RAFI CKO PROGRAMIRANJE

Slika 6.2: Prazan okvir na ekranu.

Listing 6.1: Prikaz praznog okvira.


import javax.swing.*; public class PrazanOkvir { public static void main(String[] args) { JFrame okvir = new JFrame("Prazan okvir"); okvir.setSize(300, 200); okvir.setLocation(100, 150); okvir.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); okvir.setVisible(true); } }

Razmotrimo malo detaljnije naredbe metoda main() ovog programa za prikaz praznog okvira. Na samom po cetku se okvir konstruie pozivom konstruktora klase JFrame:
JFrame okvir = new JFrame("Prazan okvir");

Konstruktor klase JFrame ima nekoliko preoptere cenih verzija, a ovde je kori cen onaj koji ima parametar za naslov okvira koji se prikazuje na vrhu okvira. Svaki novokonstruisani pravougaoni okvir ima podrazumevanu, prili cno beskorisnu veli cinu irine 0 i visine 0 piksela.1 Zbog toga se u primeru nakon konstruisanja okvira odmah odreduje njegova veli cina (300 200 pik sela) i njegova pozicija na ekranu (gornji levi ugao okvira je u ta cki s koordinatama 100 i 150 piksela). Objektni metodi u klasi JFrame za podeavanje veli cine okvira i njegove pozicije na ekranu su setSize() i setLocation():
okvir.setSize(300, 200); okvir.setLocation(100, 150);
1 Sve gra cke dimenzije u Javi se navode u jedinicama koje ozna cavaju pojedina cne ta cke na ekranu. Ove ta cke se tehni cki zovu pikseli.

6.2. Gra cki elementi

169

Naredni korak je odredivanje reakcije programa kada korisnik zatvori ok vir pritiskom na dugme u gornjem desnom uglu okvira. U ovom primeru se prosto zavrava rad programa:
okvir.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

U sloenijem programu sa vie okvira verovatno ne treba zavriti njegov rad samo zbog toga to je korisnik zatvorio jedan od okvira. Ina ce, ukoliko se eksplicitno ne odredi ta treba dodatno uraditi kada se okvir zatvori, program se ne zavrava nego se samo okvir sakriva tako to se u cini nevidljivim. Konstruisanjem okvira se on automatski ne prikazuje na ekranu. Novi okvir se nalazi u memoriji i nevidljiv je kako bi mu se u meduvremenu mogle dodati druge komponente, pre prvog prikazivanja na ekranu. Da bi se okvir prikazao na ekranu, za njega se poziva metod setVisible() klase JFrame sa argumentom true:
okvir.setVisible(true);

Obratite panju na to da se nakon ove naredbe zavrava metod main(), ali se time ne zavrava rad programa. Program nastavlja da se izvrava (ta cnije, i dalje se izvrava jedna njegova nt za prosledivanje dogadaja) sve dok se ne zatvori njegov glavni okvir ili se ne izvri metod System.exit() usled neke greke. Sama klasa JFrame ima samo nekoliko metoda za menjanje izgleda okvira. Ali u bibliotekama AWT i Swing je nasledivanjem denisana relativno velika hijerarhija odgovarajau cih klasa tako da JFrame nasleduje mnoge metode od svojih natklasa. Na primer, neki od zna cajnijih metoda za podeavanje okvira koji se mogu na ci u raznim natklasama od JFrame, pored onih ve c pomenutih, jesu: Metod setLocationByPlatform() sa argumentom true preputa kontrolu operativnom sistemu da po svom nahodenju izabere poziciju (ali ne i veli cinu) okvira na ekranu. Metod setBounds() slui za pozicioniranje okvira i istovremeno odredivanje njegove veli cine na ekranu. Na primer, dve naredbe:
okvir.setSize(300, 200); okvir.setLocation(100, 150);

mogu se zameniti jednom:


okvir.setBounds(100, 150, 300, 200);

Metod setTitle() slui za promenu teksta u naslovu okvira. ckog tipa odreduje da li se Metod setResizable() sa argumentom logi moe promeniti veli cina okvira.

170

6. G RAFI CKO PROGRAMIRANJE

Metod setIconImage() slui za odredivanje slike ikone na vrhu okvira. na cina na koji se komponente Metod setLayout() slui za odredivanje razmetaju unutar okvira.

Kontejneri i komponente
Elementi na kojima se zasniva gra cko programiranje u Javi su kontejneri i komponente. Kontejneri se mogu prikazati na ekranu i slue za grupisanje komponenti, a komponente se jedino mogu prikazati unutar nekog kontejnera. Dugme je primer jedne komponente, dok je okvir primer kontejnera. Da bi se prikazalo dugme, ono se najpre mora dodati nekom okviru i zatim se taj okvir mora prikazati na ekranu. Mogu ci kontejneri i komponente u Javi su naravno predstavljeni odgovaraju cim klasama c ija je hijerarhija denisana u bibliotekama AWT i Swing. Na slici 6.3 je prikazan mali deo ovog stabla nasledivanja koji je relevantan za osnovne gra cke elemente.2
Object

Component

Container

JComponent

Window

JPanel

Frame

JFrame

Slika 6.3: Hijerarhija klasa AWT/Swing gra ckih elemenata. Klase Component i Container u biblioteci AWT su apstraktne klase od kojih se grana cela hijerarhija. Primetimo da je Container dete od Component, to
2 Nalaenje nekog metoda koji je potreban za rad sa gra ckim elementima je oteano

zbog vie nivoa nasledivanja. Naime, traeni metod ne mora biti u klasi koja predstavlja odgovaraju ci element, ve c se moe nalaziti u nekoj od nasledenih klasa na viem nivou. Zato je dobro snalaenje u Java dokumentaciji od velike vanosti za gra cko programiranje.

6.2. Gra cki elementi

171

zna ci da neki kontejner jeste i komponenta. Kako se komponenta moe dodati u kontejner, ovo dalje implicira da se jedan kontejner moe dodati u drugi kontejner. Mogu cnost ugnjedavanja kontejnera je vaan aspekt pravljenja dobrog i lepog gra ckog korisni ckog interfejsa. Klasa JComponent u biblioteci Swing je dete klase Container i predak svih komponenti predstavljenih klasama JButton, JLabel, JComboBox, JMenuBar i tako dalje. Prema tome, sve Swing komponente su i kontejneri, pa se mogu medusobno ugnjedavati. Dva osnovna kontejnera za komponente u biblioteci Swing su okvir i panel predstavljeni klasama JFrame i JPanel. Okvir je, kao to je ve c spomenuto, predviden da bude prozor najvieg nivoa. Njegova struktura je relativno sloena i svi njeni detalji prevazilaze ciljeve ove knjige. Napomenimo samo da jedna od posledica specijalnog statusa okvira kao glavnog prozora jeste to to se on ne moe dodavati drugim kontejnerima i komponentama, iako je klasa JFrame naslednica od Container i dakle od Component. Prostija verzija kontejnera opte namene je panel koji se moe dodavati drugim kontejnerima. Panel je nevidljiv kontejner i mora se dodati kontejneru najvieg nivoa, recimo okviru, da bi se mogao videti na ekranu. Jedan panel se moe dodavati i drugom panelu, pa se onda ugnjedavanjem pa nela moe na relativno lak na cin posti ci grupisanje komponenti i njihov eljeni raspored unutar nekog kontejnera vieg nivoa. Ova primena panela kao (pot)kontejnera je najrasprostranjenija u programima, ali budu ci da je klasa JPanel naslednica klase JComponent, panel ujedno predstavlja i Swing komponentu, to zna ci da panel moe sluiti i za prikazivanje (crtanje) informacija. Komponenta je vizuelni gra cki objekat koji slui za prikazivanje (crtanje) informacija. Programeri mogu denisati sopstvene komponente na jednostavan na cin proirivanjem klase JComponent. Biblioteka Swing sadri i veliki broj gotovih komponenti koje su predstavljene odgovaraju cim klasama naslednicama klase JComponent. Na primer, standardnim komponentama kao to su dugme, oznaka, tekstalno polje, polje za potvrdu, radio dugme i kombinovano polje odgovaraju klase JButton, JLabel, JTextField, JCheckBox, JRadioButton i JComboBox. (Ovo su samo neke od osnovnih komponenti, a potpun spisak svih komonenti u biblioteci AWT/Swing je daleko ve ci.) Na slici 6.4 je prikazan izgled ovih komponenti unutar glavnog okvira programa. U svakoj od klasa standardnih komponenti denisano je nekoliko konstruktora koji se koriste za konstruisanje objekata konkretnih komponenti. Na primer, komponente na slici 6.4 mogu se konstruisati na slede ci na cin:
// Dugme sa tekstom "OK" JButton dugmeOK = new JButton("OK");

172

6. G RAFI CKO PROGRAMIRANJE

Slika 6.4: Neke standardne GUI komponente iz biblioteke Swing.

// Oznaka sa tekstom "Unesite svoje ime: " JLabel oznakaIme = new JLabel("Unesite svoje ime: "); // Tekstualno polje sa tekstom "Ovde upiite svoje ime" JTextField tekstPoljeIme = new JTextField("Ovde upiite svoje ime"); // Polje za potvrdu sa tekstom "Student" JCheckBox potvrdaStudent = new JCheckBox("Student"); // Radio dugme sa tekstom "Zaposlen" JRadioButton radioDugmeZaposlen = new JRadioButton("Zaposlen"); // Kombinovano polje sa tekstom godine studija JComboBox kombPoljeGodina = new JComboBox(new String[]{"I godina", "II godina", "III godina", "IV godina"});

Dodavanje komponente nekom kontejneru, kao i dodavanje jednog kontejnera drugom kontejneru, vri se metodom add(). Ovaj metod ima vie preoptere cenih verzija, ali u najjednostavnijem obliku se kao njegov argument navodi komponenta ili kontejner koji se dodaje kontejneru. Na primer, ako promenljiva panel ukazuje na panel tipa JPanel, onda se standardna komponenta dugme moe dodati tom panelu naredbom:
panel.add(new JButton("Test"));

Paneli se mogu nalaziti unutar okvira ili drugog panela, pa se prethodni panel sa dugmetom moe dodati jednom okviru tipa JFrame, recimo, na koga ukazuje promenljiva okvir:
okvir.add(panel);

6.2. Gra cki elementi

173

Koordinate gra ckih elemenata


Sve komponente i kontejneri su pravougaonog oblika koji imaju veli cinu (irinu i visinu) i poziciju na ekranu. Ove vrednosti se u Javi izraavaju u jedinicama koje predstavljaju najmanje ta cke za crtanje na ekranu. Tehni cko ime ove jedinice je piksel, to je kovanica od engleskog termina picture element. Ekran ra cunara se zi cki sastoji od dvodimenzionalne matrice ta caka sa odredenim brojevima redova i kolona koji zavise od rezolucije ekrana. Tako, na primer, rezolucija ekrana 1024 768 ozna cava da je ekran podeljen u 1024 reda i 768 kolona u c ijim presecima se nalazi po jedna ta cka (piksel) za crtanje slike. Ovakva zi cka organizacija ekrana se logi cki predstavlja koordinatnim sistemom koji obrazuju pikseli. Koordinatni po cetak (0, 0) se smatra da se nalazi u gornjem levom uglu ekrana. Svaka ta cka na ekranu je logi cki dakle piksel s koordinatama (x , y ), gde je x broj piksela desno i y broj piksela dole od koordinatnog po cetka (gornjeg levog ugla ekrana). Pretpostavimo na primer da je u programu konstruisan okvir tipa JFrame na koga ukazuje promenljiva okvir i da mu je metodom setBounds() odredena pozicija i veli cina:
okvir.setBounds(100, 150, 300, 200);

Gornji levi ugao ovog okvira se nalazi u ta cki s koordinatama (100, 150) u odnosu na gornji levi ugao ekrana. irina okvira je 300 piksela i visina okvira je 200 piksela. Izgled celog ekrana za ovaj primer je prikazan na slici 6.5.

Ekran
(0, 0) 150 100 (0, 0) y

X osa

Okvir
300 x

200

(x , y )

Y osa Slika 6.5: Koordinatni sistem ekrana i komponenti. Kada se komponenta doda nekom kontejneru, njena pozicija unutar kon-

174

6. G RAFI CKO PROGRAMIRANJE

tejnera se odreduje na osnovu gornjeg levog ugla kontejnera. Drugim re cima, gornji levi ugao kontejnera je koordinatni po cetak (0, 0) relativnog koordinatnog sistema kontejnera, a pozicije komponente se odreduje u odnosu na koordinatni po cetak kontejnera, a ne ekrana. U narednom primeru se najpre konstruie dugme, zatim mu se odreduju pozicija i veli cina i, na kraju, dodaje se okviru:
JButton dugme = new JButton("OK"); // OK dugme dugme.setBounds(20, 100, 60, 40); // granice OK dugmeta okvir.add(dugme); // dodavanje OK dugmeta u okvir

Gornji levi ugao dugmeta se nalazi u ta cki (20, 100) relativno u odnosu na gornji levi ugao okvira kome se dodaje. (Ovde se pretpostavlja da se za okvir ne koristi nijedan od unapred raspoloivih na cina za razmetanje komponenti.) irina dugmeta je 60 piksela i njegova visina je 40 piksela. Relativni koordinatni sistem se koristi zato to se prozori mogu pomerati na ekranu. U prethodnom primeru je gornji levi ugao okvira potencijalno promenljiv, ali relativna pozicija dugmeta unutar okvira se ne menja. Na taj na cin su programeri oslobodeni velike obaveze da prera cunavaju relativne pozicije komponenti unutar kontejnera u njihove apsolutne koordinate na ekranu kako se prozor pomera.

Raspored komponenti unutar kontejnera


Prilikom dodavanja komponenti u kontejner, one se unutar kontejnera razmetaju na odreden cin. To zna ci da se po unapred denisanom po na stupku odreduje veli cina i pozicija komponenti unutar kontejnera. Svakom kontejneru je pridruen podrazumevani na cin razmetanja komponenti unutar njega, ali se to moe promeniti ukoliko se eljeni na cin razmetanja navede kao argument metoda setLayout() klase Container:
public void setLayout(LayoutManager m)

Iz zaglavlja ovog metoda se vidi da je postupak za razmetanje komponenti u Javi predstavljen objektom tipa LayoutManager. LayoutManager je interfejs koji sve klase odgovorne za razmetanje komponenti moraju implementirati. To nije mali zadatak, ali u principu programeri mogu pisati sopstvene postupke za razmetanje komponenti. Mnogo c e ci slu caj je ipak da se koristi jedan od gotovih na cina koji su raspoloivi u biblioteci AWT/Swing: FlowLayout GridBagLayout SpringLayout GridLayout CardLayout OverlayLayout BorderLayout BoxLayout

6.2. Gra cki elementi

175

U optem slu caju dakle, komponente se smetaju unutar kontejnera, a njegov pridrueni postupak razmetanja (engl. layout manager ) odreduje po ziciju i veli cinu komponenti u kontejneru. Detaljan opis svih raspoloivih postupaka za razmetanje komponenti u Javi bi oduzeo mnogo prostora u ovoj knjizi, pa c emo u nastavku vie panje posvetiti samo onim osnovnim. FlowLayout. Ovo je najjednostavniji postupak za razmetanje komponenti. Komponente se smetaju sleva na desno, kao re ci u re cenici, onim redom kojim su dodate u kontejner. Svaka komponenta dobija svoju prirodnu veli cinu i prenosi se u naredni red ako ne moe da stane do irine kontejnera. Pri tome se moe kontrolisati da li komponente treba da budu levo poravnate, desno poravnate, ili poravnate po sredini, kao i to koliko treba da bude njihovo medusobno horizontalno i vertikalno rastojanje. U narednom programu, na primer, jednom panelu se dodaje est dugmadi koja se automatski razmetaju po FlowLayout postupku, jer je to podrazumevani na cin za razmetanje komponenti u svakom panelu.
import javax.swing.*; public class TestFlowLayout { public static void main(String[] args) { // Konstruisanje okvira JFrame okvir = new JFrame("Test FlowLayout"); okvir.setSize(300, 200); okvir.setLocation(100, 150); okvir.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Konstruisanje est dugmadi JButton crvenoDugme = new JButton("Crveno"); JButton zelenoDugme = new JButton("Zeleno"); JButton plavoDugme = new JButton("Plavo"); JButton narandastoDugme = new JButton("Narandasto"); JButton beloDugme = new JButton("Belo"); JButton crnoDugme = new JButton("Crno"); // Konstruisanje panela za dugmad JPanel panel = new JPanel(); // Smetanje dugmadi u panel panel.add(crvenoDugme); panel.add(zelenoDugme); panel.add(plavoDugme); panel.add(narandastoDugme);

176

6. G RAFI CKO PROGRAMIRANJE

panel.add(beloDugme); panel.add(crnoDugme); // Smetanje panela u okvir okvir.add(panel); okvir.setVisible(true); } }

Na slici 6.6 je prikazan okvir koji se dobija izvravanjem ovog programa. Kao to se moe videti sa te slike, komponente su poravnate po sredini i prenose se u novi red kada nema vie mesta u panelu.

Slika 6.6: Panel sa est dugmadi razmetenih po FlowLayout postupku. Pored toga, ukoliko se pomeranjem ivica okvira promeni njegova veli cina, komponente u okviru se automatski razmetaju potuju ci FlowLayout postupak. Ova c injenica je ilustrovana na slici 6.7.

Slika 6.7: Automatsko razmetanje dugmadi zbog promene veli cine okvira.

GridLayout. Ovim postupkom se kontejner deli u matricu polja po redovima i kolonama. Jedna komponenta se smeta u jedno od ovih polja tako da sve komponente imaju istu veli cinu. Brojevi redova i kolona polja za smetanje komponenti se odreduju argumentima konstruktora klase GridLayout,

6.2. Gra cki elementi

177

kao i medusobno horizontalno i vertikalno rastojanje izmedu polja. Komponente se u kontejner smetaju sleva na desno u prvom redu, pa zatim u drugom redu i tako dalje, onim redom kojim su dodate u kontejner. U narednom programu, na primer, panelu se dodaje est dugmadi koja se u njemu razmetaju po GridLayout postupku.
import javax.swing.*; import java.awt.*; public class TestGridLayout { public static void main(String[] args) { // Konstruisanje okvira JFrame okvir = new JFrame("Test GridLayout"); okvir.setSize(300, 200); okvir.setLocation(100, 150); okvir.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Konstruisanje vie dugmadi JButton crvenoDugme = new JButton("Crveno"); JButton zelenoDugme = new JButton("Zeleno"); JButton plavoDugme = new JButton("Plavo"); JButton narandastoDugme = new JButton("Narandasto"); JButton beloDugme = new JButton("Belo"); JButton crnoDugme = new JButton("Crno"); // Konstruisanje panela za dugmad JPanel panel = new JPanel(); // Pridruivanje postupka GridLayout za razmetanje // komponenti u panel u 3 reda i 2 kolone, sa horizontalnim // i vertikalnim rastojanjem 5 i 10 piksela izme du njih panel.setLayout(new GridLayout(3, 2, 5, 10)); // Smetanje dugmadi u panel panel.add(crvenoDugme); panel.add(zelenoDugme); panel.add(plavoDugme); panel.add(narandastoDugme); panel.add(beloDugme); panel.add(crnoDugme); // Smetanje panela u okvir okvir.add(panel); okvir.setVisible(true); } }

178

6. G RAFI CKO PROGRAMIRANJE

Na slici 6.8 je prikazan okvir koji se dobija izvravanjem ovog programa. Kao to se moe videti sa te slike, komponente su iste veli cine i smetene su u 3 reda i 2 kolone. Horizontalno i vertikalno rastojanje izmedu njih je 5 i 10 piksela. Obratite panju na to kako su ove vrednosti navedene u pozivu metoda setLayout() za panel:
new GridLayout(3, 2, 5, 10)

Ovim izrazom se konstruie (anonimni) objekat tipa GridLayout, pri c emu su eljeni brojevi redova i kolona, kao i horizontalno i vertikalno rastojanje izmedu njih, navode kao argumenti konstruktora klase GridLayout.

Slika 6.8: Panel sa est dugmadi razmetenih po GridLayout postupku.. U slu caju postupka GridLayout, ukoliko se promeni veli cinu okvira, matri cni poredak komponenti se ne menja (tj. broj redova i kolona polja ostaje isti, kao i rastojanje izmedu injenica je ilustovana na slici 6.9. njih). Ova c

Slika 6.9: Promena veli cine okvira ne uti ce na razmetanje komponenti.

BorderLayout. Ovim postupkom se kontejner deli u pet polja: isto cno, zapadno, severno, juno i centralno. Svaka komponenta se dodaje u jedno od ovih polja metodom add() koriste ci dodatni argument koji predstavlja jednu od pet konstanti:

6.2. Gra cki elementi

179

BorderLayout.EAST BorderLayout.NORTH BorderLayout.CENTER

BorderLayout.WEST BorderLayout.SOUTH

Komponente se smetaju u njihovoj prirodnoj veli cini u odgovaraju ce polje. Pri tome, severno i juno polje se mogu automatski horizontalno rairiti, isto cno i zapadno polje se mogu automatski vertikalno rairiti, dok se centralno polje moe automatski rairiti u oba pravca radi popunjavanja praznog prostora. U narednom programu se dodaje pet dugmadi u panel koja se u njemu razmetaju po BorderLayout postupku.
import javax.swing.*; import java.awt.*; public class TestBorderLayout { public static void main(String[] args) { // Konstruisanje okvira JFrame okvir = new JFrame("Test BorderLayout"); okvir.setSize(300, 200); okvir.setLocation(100, 150); okvir.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Konstruisanje pet dugmadi JButton isto cnoDugme = new JButton("Isto cno"); JButton zapadnoDugme = new JButton("Zapadno"); JButton severnoDugme = new JButton("Severno"); JButton junoDugme = new JButton("Juno"); JButton centralnoDugme = new JButton("Centralno"); // Konstruisanje panela za dugmad JPanel panel = new JPanel(); // Pridruivanje postupka BorderLayout za razmetanje // komponenti u panel, sa horizontalnim i vertikalnim // rastojanjem 5 i 10 piksela izme du njih panel.setLayout(new BorderLayout(5, 10)); // Smetanje dugmadi u panel panel.add(isto cnoDugme, BorderLayout.EAST); panel.add(zapadnoDugme, BorderLayout.WEST); panel.add(severnoDugme, BorderLayout.NORTH); panel.add(junoDugme, BorderLayout.SOUTH); panel.add(centralnoDugme, BorderLayout.CENTER); // Smetanje panela u okvir

180

6. G RAFI CKO PROGRAMIRANJE

okvir.add(panel); okvir.setVisible(true); } }

Na slici 6.10 je prikazan okvir koji se dobija izvravanjem ovog programa. Kao to se moe videti sa te slike, severno i juno dugme se automatski horizontalno prostiru celom irinom okvira, zapadno i isto cno dugme se automatski vertikalno prostiru po raspoloivoj visini, dok je centralno dugme automatski proireno u oba pravca.

Slika 6.10: Panel sa pet dugmadi razmetenih po BorderLayout postupku. Kod postupka BorderLayout nije neophodno da se svako polje popuni nekom komponentom. U slu caju da neko polje nije popunjeno, ostala polja c e automatski zauzeti njegovo mesto prema tome u kom pravcu mogu da se proiruju. Na primer, ako se u prethodnom programu ukloni isto cno dugme, onda c e se centralno dugme proiriti udesno da bi se zauzeo slobodan prostor. Isto tako, ukoliko se promeni veli cina okvira, komponente se automatski proiruju u dozvoljenom pravcu. Ova c injenica je ilustrovana na slici 6.11.

Slika 6.11: Promena veli cine okvira izaziva proirivanje komponenti u dozvoljenom pravcu.

6.3. Denisanje gra ckih komponenti

181

6.3 Denisanje gra ckih komponenti


Ukoliko je potrebno prikazati informacije na poseban na cin koji se ne moe posti ci unapred denisanim komponentama, moraju se denisati nove gra cke komponente. To se postie denisanjem klase nove komponente tako da bude naslednica klase JComponent. Pri tome se mora nadja cati metod paintComponent() klase JComponent, a u nadja canoj verziji ovog metoda se mora denisati ta se crta u novoj komponenti:
public class Grafi ckaKomponenta extends JComponent { public void paintComponent(Graphics g) { // Naredbe za crtanje u komponenti . . . } }

U optem slu caju, svaka gra cka komponenta je odgovorna za sopstveno crtanje koje se izvodi u metodu paintComponent(). To vai kako za standardne, tako i za nove komponente koje se posebno deniu. Dodue, standardne komponente se samo dodaju odgovaraju cem kontejneru, jer je za njih u biblioteci AWT/Swing ve c denisano ta se crta na ekranu. Obratite panju na to da se prikazivanje informacija u nekoj komponenti i prikazivanje teksta u kompoizvodi jedino crtanjem u toj komponenti. (Cak nenti izvodi se njegovim crtanjem.) Ovo crtanje se obavlja u komponentinom metodu paintComponent() koji ima jedan parametar tipa Graphics. U stvari, kompletno crtanje u ovom metodu mora se izvoditi samo preko tog njegovog parametra tipa Graphics. Klasa Graphics je apstraktna klasa koja u programu obezbeduje gra cke mogu cnosti nezavisno od zi ckog gra ckog uredaja. Svaki put kada se neka komponenta (standardna ili programerski denisana) prikae na ekranu, JVM automatski konstruie objekat tipa Graphics koji predstavlja gra cki kontekst za tu komponentu na konkretnoj platformi. Da bi se bolje razumela svrhu klase Graphics, dobra analogija je ukoliko se jedna gra cka komponenta uporedi sa listom papira, a odgovaraju ci objekat tipa Graphics sa olovkom ili c etkicom: metodi klase Graphics (mogu cnosti olovke ili c etkice) mogu se koristiti za crtanje u komponenti (na listu papira). Klasa Graphics sadri objektne metode za crtanje stringova, linija, pravougaonika, ovala, lukova, poligona i poligonalnih linija. Na primer, za crtanje stringa (tj. prikazivanje teksta) slui metod:
public void drawString(String s, int x, int y)

182

6. G RAFI CKO PROGRAMIRANJE

gde su x i y (relativne) koordinate mesta po cetka stringa s u komponenti. Sli cno, za crtanje pravougaonika slui metod:
public void drawRect(int x, int y, int w, int h)

gde su x i y (relativne) koordinate gornjeg levog ugla pravougaonika, a w i h njegova irina i visina. (Detalje o ostalim metodima c itaoci mogu potraiti u Java dokumentaciji.) Radi ilustracije, slede ci program crta prigodan tekst i pravougaonik unutar okvira koji su prikazani na slici 6.12.
import javax.swing.*; import java.awt.*; public class TestGraphics { public static void main(String[] args) { // Konstruisanje okvira JFrame okvir = new JFrame("Test Graphics"); okvir.setSize(300, 200); okvir.setLocation(100, 150); okvir.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Konstruisanje komponente Grafi ckaKomponenta komp = new Grafi ckaKomponenta(); // Smetanje komponente u okvir okvir.add(komp); okvir.setVisible(true); } } class Grafi ckaKomponenta extends JComponent { public void paintComponent(Graphics g) { g.drawString("Java je zabavna za programiranje!", 20, 50); g.drawRect(50, 80, 150, 50); } }

Primetimo da se program sastoji od glavne klase TestGraphics i klase komponente Grafi ckaKomponenta. Denicija klase Grafi ckaKomponenta ne sadri nijedan specikator pristupa kako bi se obe klase u ovom jednostavnom programu mogle nalaziti u istoj datoteci. Naime, ukoliko datoteka sa Java kodom sadri vie klasa, onda samo jedna od njih moe biti public. Obratite posebno panju na to da se u programu nigde eksplicitno ne poziva metod paintComponent() klase Grafi ckaKomponenta. U stvari, ovaj

6.3. Denisanje gra ckih komponenti

183

Slika 6.12: Rezultati nekih metoda klase Graphics.

metod nikad ne treba pozivati direktno, jer se on automatski poziva kada treba prikazati glavni okvir programa. Preciznije, svaki put kada se prikazuje glavni okvir programa, bez obzira na razlog, JVM implicitno poziva za izvravanje metode paintComponent() svih komponenti u tom okviru. Koji su mogu ci razlozi za (ponovno) prikazivanje okvira programa? Naravno, glavni razlog je kada se okvir prikazuje po prvi put nakon pokretanja programa. Ali i druge akcije korisnika izazivaju automatsko crtanje komponenti pozivom metoda paintComponent(). Na primer, minimizovanjem pa otvaranjem okvira programa, njegov sadraj se mora ponovo nacrtati. Ili, ako se otvaranjem drugog prozora prekrije (delimi cno ili potpuno) postoje ci okvir, pa se ovaj okvir ponovo izabere u fokusu, tada se sadraj postoje ceg okvira mora opet nacrtati. U klasi JComponent je dakle denisan metod
protected void paintComponent(Graphics g)

koji svaka nova komponenta treba da nadja ca da bi se obezbedilo njeno crtanje. JVM automatski poziva ovu nadja canu verziju svaki put kada komponentu treba (ponovno) prikazati. Pri tome, JVM automatski konstruie i propisno inicijalizuje objekat g tipa Graphics za svaku vidljivu komponentu i prenosi ga metodu paintComponent() radi crtanja u komponenti, nezavisno od konkretnog gra ckog uredaja na kome se zi cki izvodi crtanje. Poto metod paintComponent() ne treba direktno pozivati za crtanje u komponenti, ta uraditi u slu caju kada je neophodno hitno prikazati sadraj komponente negde u sredini nekog drugog metoda? Prikazivanje komponente se moe zahtevati metodom
public void repaint()

koji je denisan u klasi Component, pa ga moe koristiti svaka komponenta. U stvari, pozivom ovog metoda se ne izvrava neposredno crtanje komponente, nego se samo obavetava JVM da je potrebno ponovo nacrtati komponentu.

184

6. G RAFI CKO PROGRAMIRANJE

JVM c e ovaj zahtev registrovati i pozvati metod paintComponent() komponente to pre moe, odnosno c im obradi eventualne prethodne zahteve.

6.4 Klase za crtanje


Osnovna klasa Graphics za crtanje u Javi postoji od po cetne verzije jezika. Ona sadri metode za crtanje teksta, linija, provougaonika i drugih geometrijskih oblika. Ipak te gra cke operacije su prili cno ograni cene jer, na primer, nije mogu ce menjati debljinu linija ili rotirati geometrijske oblike. Zbog toga je od verzije Java 1.2 dodata klasa Graphics2D s mnogo ve cim gra ckim mogu cnostima. Uz to su dodate i druge prate ce klase, pa se cela kolekcija dodatih klasa jednim imenom naziva biblioteka Java 2D. U ovom odeljku se govori samo o osnovama biblioteke Java 2D, a vie informacije o njoj c itaoci mogu potraiti u dodatnoj literaturi. Klasa Graphics2D nasleduje klasu Graphics, to zna ci da se svi posto je ci metodi klase Graphics mogu primeniti i na objekte klase Graphics2D. Dodatno, metodu paintComponent() se od verzije Java 1.2 prenosi zapravo objekat stvarnog tipa Graphics2D, a ne tipa Graphics. To je neophodno kako bi se u ovom metodu mogle koristiti sloenije gra cke operacije denisane u klasi Graphics2D. Primetimo da se ovim ne naruava denicija metoda paintComponent() u klasi JComponent, jer na osnovu principa podtipa za nasledivanje, parametar tipa Graphics ovog metoda moe ukazivati na objekat podtipa Graphics2D. Medutim, za kori cenje svih gra ckih mogu cnosti klase Graphics2D u me todu paintComponent(), mora se izvriti ekplicitna konverzija tipa njegovog parametra na uobi cajeni na cin:
public void paintComponent(Graphics g) { Graphics2D g2 = (Graphics2D) g; // Koristiti metode za crtanje objekta g2, a ne g . . . }

Crtanje u biblioteci Java 2D se generalno zasniva na geometrijskim objektima u klasama iz paketa java.awt.geom koje implementiraju interfejs Shape. Medu ce apstraktne klase: ovim klasama se nalaze slede Line2D za linije; Rectangle2D za pravougaonike;

6.4. Klase za crtanje

185

Ellipse2D za elipse i krugove; Arc2D za lukove; i CubicCurve2D za takozvane Bezijeove krive. U klasi Graphics2D, izmedu ostalog, denisani su i metodi
void draw(Shape s) void fill(Shape s)

koji slue za crtanje konture geometrijskog oblika s i za popunjavanje njegove unutranjosti koriste ci aktuelna svojstva gra ckog konteksta tipa Graphics2D. Jedno poboljanje koje donosi biblioteke Java 2D su ta cnije jedinice u kojima se navode koordinate gra ckih elemenata. Koordinate na stari na cin se predstavljaju celobrojnim vrednostima za ozna cavanje piksela na ekranu. Nasuprot tome, u biblioteci Java 2D se koriste realni brojevi za koordinate koji ne moraju ozna cavati piksele, ve c uobi cajene duinske jedinice (na primer, milimetre ili in ce). U internim izra cunavanjima biblioteke Java 2D koriste se realni brojevi obi cne preciznosti tipa float. Ta preciznost je sasvim dovoljna za ozna cavanje piksela na ekranu ili papiru. Pored toga, izra cunavanja sa float vrednostima se bre izvode nego sa double vrednostima, a i float vrednosti zauzimaju duplo manje memorije od double vrednosti. Medutim, u Javi se podra zumeva da su realni literali tipa double, a ne tipa float, pa se mora dodavati slovo F na kraju konstante tipa float. Slede ce naredba dodele, na primer,
float x = 2.34; // GREKA!

pogrena je zato to se podrazumeva da je realni broj 2.34 tipa double. Ova naredba ispravno napisana je:
float x = 2.34F; // OK

Pored toga, u radu sa nekim gra ckim operacijama je potrebno koristiti eksplicitnu konverziju iz tipa double u tip float. Sve ovo je razlog zato se u biblioteci Java 2D mogu na ci dve verzije za svaki geometrijski element: jedna verzija koristi koordinate tipa float i druga verzija koristi koordinate tipa double. Razmotrimo primer geometrijskog oblika pravougaonika koji je predstavljen klasom Rectangle2D. Ovo je apstraktna klasa koju nasleduju dve kon 3 kretne klase: Rectangle2D.Float Rectangle2D.Double

3 To su zapravo stati cke ugnjedene klase unutar apstraktne klase Rectangle2D da se ne bi koristila imena kao to su recimo FloatRectangle2D i DoubleRectangle2D.

186

6. G RAFI CKO PROGRAMIRANJE

Kada se konstruie pravougaonik tipa Rectangle2D.Float navode se koordinate njegovog gornjeg levog ugla i njegova irina i visina kao float vrednosti. Za pravougaonik tipa Rectangle2D.Double ove veli cine se izraavaju kao double vrednosti:
Rectangle2D.Float p1 = new Rectangle2D.Float(50.0F, 100.0F, 22.5F, 45.5F); Rectangle2D.Double p2 = new Rectangle2D.Double(50.0, 100.0, 22.5, 45.5);

U stvari, poto klase Rectangle2D.Float i Rectangle2D.Double nasleduju zajedni cku klasu Rectangle2D i metodi u ove dve klase nadja cavaju me tode u klasi Rectangle2D, nema potrebe pamtiti ta can tip pravougaonika. Naime, na osnovu principa podtipa za nasledivanje, promenljiva klasnog tipa Rectangle2D moe ukazivati na pravougaonike oba tipa:
Rectangle2D p1 = new Rectangle2D.Float(50.0F, 100.0F, 22.5F, 45.5F); Rectangle2D p2 = new Rectangle2D.Double(50.0, 100.0, 22.5, 45.5);

Klase Rectangle2D.Float i Rectangle2D.Double se dakle moraju koristiti samo za konstruisanje pravougaonika, dok se za dalji rad sa pravougaonicima moe svuda koristiti promenljiva zajedni ckog nadtipa Rectangle2D. Ovo to se odnosi za pravougaonike vai potpuno isto i za ostale geometrijske oblike u biblioteci Java 2D. Pored toga, umesto kori cenja odvojenih x i y koordinata, ta cke koordinatnog sistema se mogu predstaviti na vie objektno orijentisan na cin pomo cu klase Point2D. Njene dve naslednice su klase Point2D.Float i Point2D.Double koje slue za konstruisanje ta caka sa (x , y ) koordinatama tipa float i double:
Point2D t1 = new Point2D.Float(10F, 20F); Point2D t2 = new Point2D.Double(10, 20);

Mnogi konstruktori i metodi imaju parametre tipa Point2D, jer je obi cno prirodnije u geometrijskim izra cunavanjima koristiti ta cke klase Point2D.

Boje
Prilikom crtanja se obi cno koriste razne boje radi postizanja odredenog vizuelnog efekta. U Javi se mogu koristiti dva na cina za predstavljanje boja: RGB (skra cenica od engl. red, green, blue) i HSB (skra cenica od engl. hue, saturation, brightness). Podrazumevani model je RGB u kojem se svaka boja predstavlja pomo cu tri broja iz intervala 0255 koji odreduju stepen crvene, zelene i plave boje u nekoj boji.

6.4. Klase za crtanje

187

Boja u Javi je objekat klase Color iz paketa java.awt i moe se konstruisati navodenjem njene crvene, zelene i plave komponente:
Color nekaBoja = new Color(r,g,b);

gde su r, g i b brojevi (tipa int ili float) iz intervala 0255. Nove boje se c esto ne moraju posebno konstruisati, jer su u klasi Color denisane stati cke konstante c ije vrednosti predstavljaju uobi cajene boje:
Color.WHITE Color.GREEN Color.MAGENTA Color.ORANGE Color.DARK_GRAY

Color.BLACK Color.BLUE Color.YELLOW Color.LIGHT_GRAY

Color.RED Color.CYAN Color.PINK Color.GRAY

Alternativni model boja je HSB u kojem se svaka boja predstavlja pomo cu tri broja za njenu nijansu (ton), zasi cenost i sjajnost. Nijansa (ton) je osnovna boja koja pripada duginom spektru boja. Potpuno zasi cena boja je c ista boja, dok se smanjivanjem zasi cenosti dobijaju prelivi kao da se c ista boja mea sa belom bojom. Najzad, sjajnost odreduje stepen osvetljenosti boje. U Javi se ova tri HSB elementa svake boje predstavljaju realnim brojevima tipa float iz intervala 0.0F1.0F. (Podsetimo se da se literali tipa float piu sa slovom F na kraju da bi se razlikovali od realnih brojeva tipa double.) U klasi Color je denisan stati cki metod getHSBColor() koji slui za konstruisanje HSB boje:
Color nekaBoja = Color.getHSBColor(h,s,b);

Izmedu cita modela RGB i HSB nema bitne razlike. Oni su samo dva razli na cina za predstavljanje istog skupa boja, pa se u programima obi cno koristi podrazumevani RGB model. Jedno od svojstava objekta tipa Graphics2D (ili Graphics) za crtanje u komponenti jeste aktuelna boja u kojoj se izvodi crtanje. Ako je g2 objekat tipa Graphics2D koji predstavlja gra cki kontekst komponente, onda se njegova aktuelna boja za crtanje moe izabrati metodom setPaint():
g2.setPaint(c);

Argument c ovog metoda je objekat boje tipa Color. Ako treba crtati, recimo, crvenom bojom u nekoj komponenti, onda treba pisati
g2.setPaint(Color.RED);

pre naredbi za crtanje u metodu paintComponent() te komponente. Na primer:


g2.setPaint(Color.RED); g2.drawString("Ceo disk ce biti obrisan!", 50, 100);

188

6. G RAFI CKO PROGRAMIRANJE

U gra ckom kontekstu komponente se koristi ista boja za crtanje sve dok se ona eksplicitno ne promeni metodom setPaint(). Ako se eli dobiti aktuelna boja gra ckog konteksta komponente, koristi se metod getPaint(). Rezultat ovog metoda je objekat tipa Paint, a Paint je interfejs koga implementira klasa Color:
Color aktuelnaBoja = (Color) g2.getPaint();

Metod getPaint() moe biti koristan u slu cajevima kada je potrebno privremeno promeniti boju za crtanje, a zatim se vratiti na prvobitnu boju:
Color staraBoja = (Color) g2.getPaint(); g2.setPaint(new Color(0, 128, 128)); // zeleno-plava boja . . . g2.setPaint(staraBoja);

Sve komponente imaju pridruene dve boje za pozadinu i lice (prednju stranu). Generalno, pre bilo kakvog crtanja, cela komponenta se boji bojom njene pozadine. Dodue ovo vai samo za neprozirne (engl. opaque) komponente, jer postoje i prozirne (engl. transparent ) komponente kod kojih se boja pozadine ne koristi. Za menjanje boje pozadine komponente slui metod setBackground() koji je denisan u klasi Component:
NovaKomponenta k = new NovaKomponenta(); k.setBackground(Color.PINK);

Metodom setBackground() se, u stvari, samo odreduje svojstvo boje poza dine komponente, odnosno time se ne postie automatsko crtanje njene boje pozadine na ekranu. Da bi se boja pozadine komponente zaista promenila na ekranu, to se mora obezbediti u samoj komponenti u njenoj nadja canoj verziji metoda paintComponent(), na primer:
public void paintComponent(Graphics g) { Graphics2D g2 = (Graphics2D) g; if (isOpaque()) { // obojiti pozadinu g2.setPaint(getBackground()); g2.fillRect(0, 0, getWidth(), getHeight()); // Za preciznije bojenje pravougaonika komponente: // g2.fill(new // Rectangle2D.Double(0, 0, getWidth(), getHeight())); g2.setPaint(getForeground()); } // Ostale naredbe za crtanje u komponenti . . . }

6.4. Klase za crtanje

189

Boja lica komponente sli cno se moe izabrati metodom setForeground() iz klase Component. Ovim metodom se odreduje podrazumevana boja za crta nje u komponenti, jer kada se konstruie gra cki kontekst komponente, njegova aktuelna boja za crtanje dobija vrednost boje lica komponente. Obratite ipak panju na to da su boje pozadine i lica zapravo svojstva komponente, a ne njenog gra ckog konteksta.

Fontovi
Font predstavlja izgled znakova teksta odredenog pisma jedan isti znak obi cno izgleda druga cije u razli citim fontovima. Raspoloivi fontovi zavise od konkretnog ra cunara, jer neki dolaze sa operativnim sistemom, dok su drugi komercijalni i moraju se kupiti. U Javi, font je odreden imenom, stilom i veli cinom. Poto lista imena stvarnih fontova zavisi od ra cunara do ra cunara, u biblioteci AWT/Swing je denisano pet logi ckih imena fontova: Serif Dialog SansSerif DialogInput Monospaced

Nazivom Serif ozna cava se font u kojem znakovi imaju crtice (serife) na svojim krajevima koje pomau o cima da se lake prati tekst. Na primer, glavni tekst ove knjige je pisan takvim fontom, to se moe zaklju citi po, recimo, horizontalnim crticama na dnu slova n. Nazivom SansSerif ozna cava se font u kojem znakovi nemaju ove crtice. Monospaced ozna cava font u kojem svi znakovi imaju jednaku irinu. (Programski tekstovi u ovoj knjizi su na primer pisani ovakvim fontom.) Dialog i DialogInput su fontovi koji se obi cno koriste u okvirima za dijalog. Ova logi cka imena se uvek mapiraju na fontove koji zaista postoje na rac unaru. U Windows sistemu, recimo, fontu SansSerif odgovara stvarni font Arial. Stil fonta se odreduje kori cenjem stati ckih konstanti koje su denisane u klasi Font: Font.PLAIN Font.ITALIC Font.BOLD Font.BOLD + Font.ITALIC

Najzad, veli cina fonta je ceo broj, obi cno od 10 do 36, koji otprilike predstavlja visinu najve cih znakova fonta. Jedinice u kojima se meri veli cina fonta su takozvane tipografske ta cke: jedan in c (2, 54 cm) ima 72 ta cke. Podrazumevana veli cina fonta za prikazivanje (crtanje) teksta je 12.

190

6. G RAFI CKO PROGRAMIRANJE

Da bi se neki tekst prikazao u odredenom fontu, mora se najpre konstrui sati odgovaraju ci objekat klase Font iz paketa java.awt. Novi font se konstruie navodenjem njegovog imena, stila i veli cine u konstruktoru:
Font obi canFont = new Font("Serif", Font.PLAIN, 12); Font sansBold24 = new Font("SansSerif", Font.BOLD, 24);

Gra cki kontekst svake komponente ima pridruen font koji se koristi za prikazivanje (crtanje) teksta. Aktuelni font gra ckog konteksta se moe menjati metodom setFont(). Obrnuto, vrednost aktuelnog fonta se moe dobiti metodom getFont() koji vra ca objekat tipa Font. Na primer, u slede coj komponenti se prikazuje prigodan tekst c iji je izgled pokazan na slici 6.13:

Slika 6.13: Primer teksta u razli citim fontovima.

class TestFontKomponenta extends JComponent { public void paintComponent(Graphics g) { Graphics2D g2 = (Graphics2D) g; Font sansBold36 = new Font("SansSerif", Font.BOLD, 36); g2.setFont(sansBold36); // veliki font za tekst g2.drawString("Zdravo narode!", 50, 50); Font serifPlain18 = new Font("Serif", Font.PLAIN, 18); g2.setFont(serifPlain18); // manji font za tekst g2.setPaint(Color.RED); // crvena boja za crtanje g2.drawString("Java je zabavna za programiranje!", 80, 100); } }

Svaka komponenta ima takode svoj pridruen font koji se moe zadati objektnim metodom setFont() iz klase Component. Kada se konstruie gra cki kontekst za crtanje u komponenti, njegov pridruen font dobija vrednost aktuelnog fonta komponente.

6.4. Klase za crtanje

191

Primer: aplet za crtanje kamiona


Aplet je gra cki Java program koji se izvrava u brauzeru (Internet Explorer, Mozilla, Safari, . . . ) prilikom prikazivanja neke veb strane. Naime, pored instrukcija za prikaz teksta, slika i drugog sadraja, veb strane mogu imati instrukcije za izvravanje specijalnog Java programa koji se popularno naziva aplet. Izvravanje apleta unutar veb strane je omogu ceno time to svi moderni brauzeri sadre u sebi Java interpretator (JVM) pod c ijom se kontrolom izvrava aplet. Pisanje apleta nije mnogo druga cije od pisanja obi cnih gra ckih programa u Javi. Naime, struktura jednog apleta je sutinski ista kao struktura okvira klase JFrame, a i sa dogadajima se kod obe vrste programa postupa na isti na cin. Prakti cno postoje samo dve glavne razlike izmedu cnog apleta i obi programa. Jedna razlika je posledica c injenice da aplet zavisi od izgleda veb strane, pa se njegove pravougaone dimenzije ne mogu programski odrediti, nego se zadaju instrukcijama za opis veb strane koja sadri aplet. Druga, bitnija razlika je to to se aplet predstavlja klasom koja nasleduje klasu JApplet. U klasi JApplet je denisano nekoliko objektnih metoda koji su jedinstveni za aplete. Najvaniji od njih je metod init() koji se po cetno poziva prilikom izvravanja apleta od strane brauzera. Metod init() kod apleta odgovara u neku ruku metodu main() kod obi cnih programa. Metod init() slui dakle za inicijalizaciju vizuelne strukture apleta (kao i postupka rukovanja sa dogadajima) radi obezbedivanja eljene funkcionalnosti apleta. Da bismo ilustrovali na cin na koji se piu apleti u Javi, u nastavku je predstavljen primer apleta za crtanje kamiona. Izgled veb strane sa ovim apletom je prikazan na slici 6.14. Slika kamiona u apletu se crta u klasi Kamion koja predstavlja uobi cajenu gra cku komponentu obi cnog programa. Sam aplet je predstavljen klasom KamionAplet u kojoj je nadja can metod init() klase JApplet. Metod init() klase KamionAplet sadri standardne naredbe kojima se apletu dodaju eljene gra cke komponente: naslov apleta kao instancu klase JLabel i sliku kamiona kao instancu klase Kamion.
import javax.swing.*; import java.awt.*; import java.awt.geom.*; public class KamionAplet extends JApplet { public void init() { setLayout(new BorderLayout());

192

6. G RAFI CKO PROGRAMIRANJE

Slika 6.14: Veb strana sa apletom za crtanje kamiona.

JLabel naslov = new JLabel("KAMION", SwingConstants.CENTER); naslov.setFont(new Font("Serif", Font.BOLD, 20)); add(naslov, BorderLayout.NORTH); JComponent fap = new Kamion(); add(fap, BorderLayout.CENTER); } } class Kamion extends JComponent { public void paintComponent(Graphics g) { Graphics2D g2 = (Graphics2D) g; // Bojenje pozadine Color bledoPlava = new Color(0.75f, 0.750f, 1.0f); g2.setColor(bledoPlava); g2.fill(new Rectangle2D.Double(0,0,300,300)); // Crtanje asije kamiona g2.setColor(Color.RED);

6.4. Klase za crtanje

193

g2.fill(new Rectangle2D.Double(50,100,120,80)); g2.fill(new Rectangle2D.Double(170,130,80,50)); // Crtanje kabine kamiona Polygon trougao = new Polygon(); trougao.addPoint(170,100); trougao.addPoint(170,130); trougao.addPoint(200,130); g2.setColor(Color.YELLOW); g2.fillPolygon(trougao); // Crtanje zadnjeg to cka g2.setColor(Color.DARK_GRAY); g2.fill(new Ellipse2D.Double(70,160,40,40)); g2.setColor(Color.WHITE); g2.fill(new Ellipse2D.Double(80,170,20,20)); // Crtanje prednjeg to cka g2.setColor(Color.DARK_GRAY); g2.fill(new Ellipse2D.Double(190,160,40,40)); g2.setColor(Color.WHITE); g2.fill(new Ellipse2D.Double(200,170,20,20)); /* Crtanje logoa na stranici kamiona */ g2.setFont(new Font("Serif", Font.ITALIC, 25)); g2.setColor(Color.WHITE); g2.drawString("Java",70,125); g2.drawString("prevoznik",70,150); } }

Izvravanje apleta se odvija od strane brauzera, pa pored pisanja samog apleta, u odgovaraju coj veb strani je potrebno navesti instrukcije za izvravanje apleta. Ove instrukcije su deo standardnog HTML jezika koji slui za opis izgleda bilo koje veb strane. Na primer, opisu veb strane sa apletom za crtanje kamiona potrebno je na eljenom mestu dodati takozvani <applet> tag:
<applet code = "KamionAplet.class" width=300 height=300> </applet>

U ovom primeru su navedene minimalne informacije koje su potrebne za izvravanje apleta, jer iri opis <applet> taga (i HTML jezika) svakako prevazilazi okvire ove knjige. Za po cetno upoznavanje sa apletima je zato dovoljno re ci da se prethodnim primerom <applet> taga u prozoru za prikaz cele veb strane zauzima okvir veli cine 300 300 piksela. Pritom se, kada brauzer prikazuje veb stranu, u tom okviru prikazuju rezultati izvravanja Java bajtkoda koji se nalazi u datoteci KamionAplet.class.

194

6. G RAFI CKO PROGRAMIRANJE

6.5 Rukovanje dogadajima


Programiranje gra ckih programa se zasniva na dogadajima. Gra cki pro gram nema metod main() od kojeg po cinje izvravanje programa redom od prve naredbe do kraja. Gra cki program umesto toga reaguje na razli cite vrste dogadaja koji se deavaju u nepredvidljivim trenucima i po redosledu nad kojim program nema kontrolu. Dogadaje direktno generie korisnik svojim postupcima kao to su pri tisak na taster tastature ili mia, pomeranje mia i sli cno. Ovi dogadeji se prenose gra ckoj komponenti u prozoru programa u kojoj se desio dogadaj tako da se sa gledita programa moe smatrati i da komponente indirektno generiu dogadaje. Standardna komponenta dugme recimo generie dogadaj svaki put kada se pritisne na dugme. Svaki generisan dogadaj ne mora izazvati reakciju programa program moe izabrati da ignorie neke dogadaje. Dogadaju su u Javi predstavljeni objektima koji su instance klasa nasled nica po cetne klase EventObject iz paketa java.util. Kada se desi neki dogadaj, JVM konstruie jedan objekat koji grupie relevantne informacije o dogadaju. Razli citi tipovi dogadaja su predstavljeni objektima koji pripadaju razli citim klasama. Na primer, kada korisnik pritisne jedan od tastera mia, konstruie se objekat klase pod imenom MouseEvent. Taj objekat sadri relavantne informacije kao to su izvor dogadaja (tj. komponenta u kojoj se nalazi pokaziva c mia u trenutku pritiska na taster mia), koordinate ta cke komponente u kojoj se nalazi pokaziva c mia i taster na miu koji je pritisnut. Sli cno, kada se klikne na standardnu komponentu dugme, generie se akcijski dogadaj ci objekat klase ActionEvent. Klase i konstruie odgovaraju MouseEvent i ActionEvent (i mnoge druge) pripadaju hijerarhji klasa na c ijem vrhu se nalazi klasa EventObject. Nakon konstruisanja objekta koji opisuje nastali dogadaj, taj objekat se kao argument prenosi ta cno predvidenom metodu za odreden tip dogadaja. Programer uti ce na obradu dogadaja pisanjem ovih metoda koji deniu ta treba uraditi kada se dogadaj desi. Objektno orijentisan model rukovanja dogadajima u Javi se zasniva na dva glavna koncepta: 1. Izvor dogadaja. Svaka gra cka komponenta u kojoj se desio dogadaj je izvor dogadaja (engl. event source). Na primer, dugme koje je pritisnuto je izvor akcijskog dogadaja tipa ActionEvent. Izvorni objekat nekog do gadaja u programu se moe dobiti pomo cu metoda getSource(). Ovaj objektni metod se nalazi u klasi EventObject od koje po cinje cela hijerarhija klasa dogadaja. Na primer, ako je e dogadaj tipa EventObject

6.5. Rukovanje dogadajima

195

(ili nekog speci cnijeg podtipa kao to je ActionEvent), onda se referenca na objekat u kojem je nastao taj dogadaj cu moe dobiti pomo e.getSource(). 2. Rukovalac dogadaja. Rukovalac dogadaja (engl. event listener ) je obje kat koji sadri poseban metod za obradu onog tipa dogadaja koji moe da se desi u izvoru dogadaja. Da bi se taj metod u rukovaocu dogadaja zaista pozvao za obradu nastalog dogadaja, rukovalac dogadaja mora zadovoljiti dva uslova: a) Rukovalac dogadaja mora biti objekat klase koja implementira spe cijalni interfejs kako bi se obezbedilo da rukovalac dogadaja sadri odgovaraju ci metod za obradu dogadaja. b) Rukovalac dogadaja se mora eksplicitno pridruiti izvoru dogadaja kako bi se znalo po nastanku dogadaja kome treba preneti konstru isani objekat dogadaja za obradu. Na slici 6.15 je prikazan odnos izmedu klasa i interfejsa za rukovanje dogadajima u Javi.
Interfejs rukovaoca dogadaja

Izvor dogadaja

jedan ili vie

Rukovalac dogadaja

Slika 6.15: Odnos izmedu klasa i interfejsa za rukovanje dogadajima. Opti mehanizam rukovanja dogadajima u Javi moe se opisati u glavnim crtama na slede ci na cin: Rukovalac dogadaja je objekat klase koja implementira specijalni inter fejs rukovaoca dogadaja. Izvor dogadaja je objekat kome se pridruuju objekti rukovalaca doga daja da bi im se preneli objekti nastalih dogadaja. Izvor dogadaja prenosi objekte dogadaja svim pridruenim rukovao cima dogadaja kada se dogadaj stvarno desi. Objekat rukovaoca dogadaja koristi informacije iz dobijenog objekta dogadaja da bi se odredila odgovaraju ca reakcija na nastali dogadaj.

196

6. G RAFI CKO PROGRAMIRANJE

Razmotrimo sada konkretnije kako se ovaj mehanizam rukovanja dogada jima reektuje na primeru standardne komponente dugmeta. Do sada smo nau cili samo kako se dugme prikazuje u prozoru programa. Kako su paljivi c itaoci moda ve c primetili u prethodnim primerima, ako se na neko takvo dugme pritisne tasterom mia, nita se ne deava. To je zato to mu nismo pridruili nijedan rukovalac dogadajima koje dugme proizvodi kada se na njega pristisne tasterom mia. Kada se tasterom mia pritisne na dugme, ono proizvodi dogadaj tipa ActionEvent. Rukovalac ovim tipom dogadaja mora implementirati speci jalni interfejs ActionListener koji sadri samo jedan apstraktni metod c ije je ime ActionPerformed():
package java.awt.event; public interface ActionListener extends java.util.EventListener { public void ActionPerformed(ActionEvent e); }

Rukovalac akcijskog dogadaja kojeg proizvodi dugme zbog toga mora biti objekat klase koja implementira interfejs ActionListener:
public class RukovalacDugmeta implements ActionListener { . . . public void ActionPerformed(ActionEvent e) { // Reakcija na pritisak tasterom mia na dugme . . . } }

U programu jo treba konstruisati dugme i rukovalac njegovim akcijskim dogadajem, kao i uspostaviti vezu izmedu njih:
JButton dugme = new JButton("OK"); ActionListener rukovalacDugmeta = new RukovalacDugmeta(...); dugme.addActionListener(rukovalacDugmeta);

Nakon izvravanja ovog programskog fragmenta, svaki put kada se klikne na dugme, odnosno kada se desi akcijski dogadaj u dugmetu sa oznakom OK, o tome se izvetava objekat na koga ukazuje rukovalacDugmeta. Sam c in izvetavanje se sastoji od konstruisanja objekta dogadaja e tipa ActionEvent i pozivanja metoda
rukovalacDugmeta.ActionPerformed(e)

kome se kao argument prenosi novokonstruisani objekat dogadaja e. Izvoru dogadaja moe biti po potrebi pridrueno vie rukovalaca doga daja. Ako je izvor dogadaja neko dugme, na primer, onda bi se pozivao metod

6.5. Rukovanje dogadajima

197

ActionPerformed() svih pridruenih rukovalaca akcijskog dogadaja koji na

staje kada se tasterom mia pritisne na to dugme. U Javi se koristi konvencija za imena klasa dogadaja i interfejsa rukova laca dogadaja. Ime klase dogadaja je standardnog oblika <Ime>Event, a ime interfejsa rukovaoca odgovaraju ceg dogadaja je oblika <Ime>Listener. Tako, akcijski dogadaj je predstavljen klasom ActionEvent i dogadaj proizveden miem je predstavljen klasom MouseEvent. Interfejsi rukovalaca tih dogadaja su zato ActionListener i MouseListener. Sve klase dogadaja i interfejsi ru kovalaca dogadaja se nalaze u paketu java.awt.event. Pored toga, metodi kojima se rukovalac dogadaja pridruuje izvoru doga daja radi obrade odredenog tipa dogadaja, po konvenciji imaju standardna imena u obliku add<Ime>Listener(). Zato, na primer, klasa Button sadri metod
public void addActionListener(ActionListener a);

koji je kori cen u prethodnom primeru da bi se dugmetu pridruio rukovalac njegovog dogadaja. Bez sumnje, pisanje Java programa u kojima se reaguje na dogadaje obu hvata mnogo detalja. Ponovimo zato u glavnim crtama osnovne delove koji su neophodni za takav program: 1. Radi kori cenja klasa dogadaja i interfejsa rukovalaca dogadaja, na po c etku programa se mora nalaziti paket java.awt.event sa deklaracijom import. 2. Radi denisanja objekata koji obraduju odredeni tip dogadaja, mora se denisati njihova klasa koja implementira odgovaraju ci interfejs rukovaoca dogadaja (na primer, interfejs ActionListener). 3. U toj klasi se moraju denisati svi metodi koji se nalaze u interfejsu koji se implementira. Ovi metodi se pozivaju kada se desi speci cni dogadaj. 4. Objekat koji je instanca ove klase mora se pridruiti komponenti c iji se dogadaji To se radi odgovaraju cim metodom komponente obraduju. kao to je addActionListener().

Primer: brojanje pritisaka tasterom mia na dugme


Da bismo ilustrovali kompletan primer rukovanja dogadajima u Javi, u nastavku je prikazan jednostavan gra cki program koji reaguje na pritiske tasterom mia na dugme. U programu se prikazuje okvir sa standardnim dugmetom i oznakom koja prikazuje broj pritisaka tasterom mia na to dugme. Po cetni prozor programa je prikazan na slici 6.16.

198

6. G RAFI CKO PROGRAMIRANJE

Slika 6.16: Po cetni prozor za brojanje pritisaka tasterom mia na dugme.

Svaki put kada se tasterom mia pritisne na dugme, rukovalac dogadaja ActionEvent . Ovaj koji je pridruen dugmetu biva obaveten o dogadaju tipa rukovalac dogadaja kao odgovor na to treba da odbrojava ukupan broj ovih dogadaje i promeni tekst oznake koji prikazuje taj broj. Na primer, ako je tasterom mia korisnik pritisnuo 8 puta na dugme, prozor programa treba da izgleda kao to je to prikazano na slici 6.17.

Slika 6.17: Izgled prozora nakon 8 pritisaka tasterom mia na dugme. Kada se tasterom mia pritisne na dugme, objekat koji obraduje ovaj do c za jedan i da novi sadraj tog polja prikae gadaj ca polje broja treba da uve promenom teksta komponente oznaka tipa JLabel:
class RukovalacDugmeta implements ActionListener { private int broja c; // broja c pritisaka na dugme

public void actionPerformed(ActionEvent e) { broja c++; oznaka.setText("Broj pritisaka = " + broja c); } }

Glavni okvir programa je kontejner koji sadri standardne komponente za oznaku i dugme. Tekst oznake slui za prikazivanje odgovaraju ceg broja pritisaka na dugme, a dugme je vizuelni element koji se pritiska tasterom mia i proizvodi akcijske dogadaje. Glavni okvir programa je zato predstavljen

6.5. Rukovanje dogadajima

199

klasom DugmeBroja cOkvir koja proiruje klasu JFrame. Pored toga, ova klasa glavnog okvira sadri polje oznaka tipa JLabel i konstruktor za inicijalizaciju okvira:
class DugmeBroja cOkvir extends JFrame { private JLabel oznaka; // oznaka u okviru . . . // Konstruktor public DugmeBroja cOkvir() { setTitle("Brojanje pritisaka na dugme "); setSize(300, 150); setLayout(new FlowLayout(FlowLayout.CENTER, 30, 20)); oznaka = new JLabel("Broj pritisaka = 0"); add(oznaka); JButton dugme = new JButton("Pritisni me"); add(dugme); dugme.addActionListener(new RukovalacDugmeta()); } }

U konstruktoru klase DugmeBroja cOkvir se najpre odreduje naslov, ve li cina i na cin razmetanja komponenti glavnog okvira. Zatim se konstruiu komponente za oznaku i dugme sa prigodnim tekstom i dodaju okviru. Na kraju, konstruisanom dugmetu se metodom addActionListener() pridruuje rukovalac akcijskog dogadaja koji je instanca klase RukovalacDugmeta. Obratite panju na to da klasa DugmeBroja cOkvir mora da sadri posebno polje oznaka tipa JLabel. To je zato to rukovalac dogadaja dugmeta treba da promeni tekst oznake kada se tasterom mia pritisne na dugme. Poto se komponenta za oznaku mora nalaziti u kontejneru-okviru, polje oznaka ukazuje na oznaku u okviru kako bi rukovalac dogadaja dugmeta preko tog polja imao pristup do oznake u okviru. Ali to stvara mali problem. U klasi RukovalacDugmeta se ne moe koristiti polje oznaka, jer je ono privatno u klasi DugmeBroja cOkvir. Postoji vie reenja ovog problema, od najgoreg da se ovo polje u cini javnim, do najboljeg da RukovalacDugmeta bude ugnjedena klasa u klasi DugmeBroja cOkvir. Ovo je ina ce c esta primena ugnjedenih klasa o kojima smo govorili u odeljku 5.4. Objekti koji obraduju dogadaje moraju obi cno da izvedu neki postupak koji uti ce na druge objekte u programu. To se onda moe elegantno

200

6. G RAFI CKO PROGRAMIRANJE

reiti ukoliko se klasa rukovalaca dogadaja denie unutar klase objekata c ije stanje rukovalac dogadaja treba da menja. U nastavku je prikazan kompletan program za brojanje pritisaka tasterom mia na dugme u kome je klasa RukovalacDugmeta ugnjedena u klasu DugmeBroja cOkvir.
import java.awt.*; import javax.swing.*; import java.awt.event.*; // Po cetna klasa public class TestDugmeBroja c { public static void main(String[] args) { DugmeBroja cOkvir okvir = new DugmeBroja cOkvir(); okvir.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); okvir.setVisible(true); } } // Glavni okvir sa dugmetom i oznakom class DugmeBroja cOkvir extends JFrame { private JLabel oznaka; // oznaka u okviru // Ugnje dena klasa rukovaoca akcijskog doga daja private class RukovalacDugmeta implements ActionListener { private int broja c; // broja c pritisaka na dugme

public void actionPerformed(ActionEvent e) { broja c++; oznaka.setText("Broj pritisaka = " + broja c); } } // Konstruktor public DugmeBroja cOkvir() { setTitle("Brojanje pritisaka na dugme "); setSize(300, 150); setLayout(new FlowLayout(FlowLayout.CENTER, 30, 20)); oznaka = new JLabel("Broj pritisaka = 0"); add(oznaka); JButton dugme = new JButton("Pritisni me"); add(dugme);

6.5. Rukovanje dogadajima

201

dugme.addActionListener(new RukovalacDugmeta()); } }

Primetimo da se ovaj program moe jo vie pojednostaviti. Naime, klasa


RukovalacDugmeta se koristi samo jedanput u poslednjoj naredbi konstruk-

tora DugmeBroja cOkvir() radi konstruisanja objekta za obradu generisanih dogadaja dugmeta. To zna ci da se RukovalacDugmeta moe denisati da bude anonimna ugnjedena klasa. Prethodni program sa ovom izmenom postaje znatno kra ci, i zato jasniji, kao to se moe videti iz slede ceg listinga.
import java.awt.*; import javax.swing.*; import java.awt.event.*; // Po cetna klasa public class TestDugmeBroja c1 { public static void main(String[] args) { DugmeBroja cOkvir okvir = new DugmeBroja cOkvir(); okvir.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); okvir.setVisible(true); } } // Glavni okvir sa dugmetom i oznakom class DugmeBroja cOkvir extends JFrame { private JLabel oznaka; // oznaka u okviru

// Konstruktor public DugmeBroja cOkvir() { setTitle("Brojanje pritisaka na dugme "); setSize(300, 150); setLayout(new FlowLayout(FlowLayout.CENTER, 30, 20)); oznaka = new JLabel("Broj pritisaka = 0"); add(oznaka); JButton dugme = new JButton("Pritisni me"); add(dugme); // Dodavanje objekta za rukovanje doga dajem dugmeta kao // instance anonimne klase koja implementira ActionListener dugme.addActionListener(new ActionListener() { private int broja c; // broja c pritisaka na dugme

202

6. G RAFI CKO PROGRAMIRANJE

public void actionPerformed(ActionEvent e) { broja c++; oznaka.setText("Broj pritisaka = " + broja c); } }); } }

Adapterske klase
Nisu svi dogadaji tako jednostavni za obradu kao pritisak tasterom mia na dugme. Na primer, interfejs MouseListener sadri pet metoda koji se pozivaju kao reakcija na dogadaje proizvedene miem:
package java.awt.event; public interface MouseListener extends java.util.EventListener { public void mousePressed(MouseEvent e); public void mouseReleased(MouseEvent e); public void mouseClicked(MouseEvent e); public void mouseEntered(MouseEvent e); public void mouseExited(MouseEvent e); }

Metod mousePressed() se poziva c im se pritisne jedan od tastera mia, a metod mouseReleased() se poziva kada se otpusti taster mia. Tre ci metod mouseClicked() se poziva kada se taster mia pritisne i brzo otpusti, bez pomeranja mia. (U stvari, to izaziva pozivanje sva tri metoda mousePressed(), mouseReleased() i mouseClicked(), tim redom.) Metodi mouseEntered() i mouseExited() se pozivaju c im pokaziva c mia ude u pravougaonu oblast neke komponente i c im je napusti. U programu se obi cno ne mora voditi ra cuna o svim mogu cim dogada jima koji se proizvode miem, ali se u klasi rukovaoca ovim dogadajima koja implementira interfejs MouseListener moraju denisati svih pet metoda. Naravno, oni metodi koji nisu potrebni se deniu tako da imaju prazno telo metoda. Drugi primer je komponenta za okvir tipa JFrame koja je izvor dogadaja tipa WindowEvent. Klasa rukovalaca ovog dogadaja mora implementirati in terfejs WindowListener koji sadri sedam metoda:
package java.awt.event; public interface WindowListener extends java.util.EventListener { public void windowOpened(WindowEvent e); public void windowClosing(WindowEvent e); public void windowClosed(WindowEvent e);

6.5. Rukovanje dogadajima

203

public public public public }

void void void void

windowIconified(WindowEvent e); windowDeiconified(WindowEvent e); windowActivated(WindowEvent e); windowDeactivated(WindowEvent e);

Na osnovu imena ovih metoda se lako moe zaklju citi kada se koji metod poziva, osim moda za windowIconified() i windowDeiconified() koji se pozivaju kada se okvir minimizuje i vra ca iz minimizovanog stanja. Ukoliko u programu elimo da korisniku omogu cimo da se predomisli kada pokua da zatvori glavni okvir programa, onda bismo trebali da deniemo klasu rukovalaca dogadaja tipa WindowEvent koja implementira inter fejs WindowListener. U toj klasi bismo zapravo denisali samo odgovaraju ci metod windowClosing(), a ostale bismo trivijalno denisali s praznim telom:
public class Zatvara cOkvira implements WindowListener { public void windowClosing(WindowEvent e) { if (korisnik se slae) System.exit(0); } public public public public public public } void void void void void void windowOpened(WindowEvent e) {} windowClosed(WindowEvent e) {} windowIconified(WindowEvent e) {} windowDeiconified(WindowEvent e) {} windowActivated(WindowEvent e) {} windowDeactivated(WindowEvent e) {}

Da se ne bi formalno pisali nepotrebni metodi s praznim telom, svaki interfejs rukovaoca dogadaja koji sadri vie od jednog metoda ima svoju adap tersku klasu koja ga implementira tako to su svi njegovi metodi trivijalno denisani s praznim telom. Tako, klasa MouseAdapter ima svih pet trivijalno denisanih metoda interfejsa MouseListener, dok klasa WindowAdapter ima sedam trivijalno denisanih metoda interfejsa WindowListener. To zna ci da adapterska klasa automatski zadovoljava tehni cki uslov koji je u Javi potreban za klasu koja implementira neki interfejs, odnosno adapterska klasa denie sve metode interfejsa koji implementira. Ali poto svi metodi adapterske klase zapravo nita ne rade, ove klase su korisne samo za nasledi vanje. Pri tome, proirivanjem adapterske klase se mogu denisati (nadja cati) samo neki, potrebni metodi odgovaraju ceg interfejsa rukovaoca dogadaja, a ne svi. To je i razlog zato za interfejse koji sadre samo jedan metod (na primer, ActionListener) nema potrebe za adapterskom klasom.

204

6. G RAFI CKO PROGRAMIRANJE

Ako se dakle nasleduje adapterska klasa WindowAdapter u prethodnom primeru klase Zatvara cOkvira, onda treba nadja cati samo jedan metod te adapterske klase, windowClosing(), koji je bitan u primeru:
public class Zatvara cOkvira extends WindowAdapter { public void windowClosing(WindowEvent e) { if (korisnik se slae) System.exit(0); } }

Objekat tipa Zatvara cOkvira se moe pridruiti nekom okviru radi obrade njegovih dogadaja koje proizvodi:
okvir.addWindowListener(new Zatvara cOkvira());

Sada kada okvir proizvede neki dogadaj tipa WindowEvent, poziva se jedan od sedam metoda pridruenog rukovaoca tipa Zatvara cOkvira. est od tih sedam metoda ne radi nita, odnosno odgovaraju ci dogadaji se zanemaruju. Ako se okvir zatvara, poziva se metod windowClosing() koji pozivom metoda System.exit(0) zavrava program, ako to korisnik odobri. Ako se konstruie samo jedan objekat klase Zatvara cOkvira u programu, ova klasa ne mora c ak ni da se posebno denie, nego moe biti anonimna ugnjedena klasa:
okvir.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { if (korisnik se slae) System.exit(0); } });

Primer: uslovno zatvaranje glavnog okvira programa


U ovom primeru je prethodni program, koji broji pritiske tasterom mia na dugme, proiren tako da se glavni okvir programa ne zatvara bezuslovno kada korisnik pritisne na dugme u gornjem desnom uglu okvira. U tom slu caju c e se pojaviti okvir sa porukom, prikazan na slici 6.18, koji korisniku prua ansu da odustane od zatvaranja glavnog okvira i da nastavi rad. Jedina izmena koja je neophodna u programu za brojanje pritisaka tasterom mia na dugme jeste dodavanje rukovaoca dogadaja zatvaranja glav nog okvira. U programu se primenjuje postupak koji smo opisali u prethod-

6.5. Rukovanje dogadajima

205

Slika 6.18: Okvir sa porukom za zavretak rada programa.

nom odeljku za adaptersku klasu WindowAdapter. Ako se podsetimo, glavnom okviru se pridruuje rukovalac dogadaja zatvaranja okvira, a taj rukova lac je instanca anonimne ugnjedene klase. U toj klasi se nadja cava metod windowClosing() koji prikazuje okvir s prigodnom porukom i prekida rad programa ukoliko korisnik to potvrdi. Za dobijanje potvrde od korisnika da se zavri rad, u programu se koristi metod showOptionDialog() iz klase JOptionPane. Svi detalji ovog metoda nisu bitni za obradu dogadaja glavnog okvira programa i mogu se na ci u zva ni cnoj dokumentaciji jezika Java. Primetimo ipak da se podrazumevana operacija zatvaranja okvira mora izabrati da ne radi nita:
okvir.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);

To je zato to se ova operacija uvek izvodi nakon to neki rukovalac obradi dogadaj ce glavni okvir postao nevidljiv kada kori zatvaranja okvira, pa bi ina snik odustane od zavretka programa. (Podrazumevana operacija zatvaranja svakog okvira je da se okvir u cini nevidljivim.) Slede ci listing sadri kompletan program koji, pored prikazivanja broja pritisaka tasterom mia na dugme, omogu cava korisniku da se predomisli kada pritisne na dugme u gornjem desnom uglu glavnog okvira programa.
import java.awt.*; import javax.swing.*; import java.awt.event.*; // Po cetna klasa public class TestDugmeBroja c2 { public static void main(String[] args) { DugmeBroja cOkvir okvir = new DugmeBroja cOkvir(); okvir.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); // Pridruivanje rukovaoca doga daja zatvaranja okvira kao // instance anonimne klase koja proiruje WindowAdapter

206

6. G RAFI CKO PROGRAMIRANJE

okvir.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { Object[] opcija = {"Da", "Ne"}; int izabranaOpcija = JOptionPane.showOptionDialog(null, "Zaista elite da zavrite program?", "Kraj rada programa", JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, null, opcija, opcija[0]); if (izabranaOpcija == 0) System.exit(0); } }); okvir.setVisible(true); } } // Glavni okvir sa dugmetom i oznakom class DugmeBroja cOkvir extends JFrame { private JLabel oznaka; // oznaka u okviru

// Konstruktor public DugmeBroja cOkvir() { setTitle("Brojanje pritisaka na dugme "); setSize(300, 150); setLayout(new FlowLayout(FlowLayout.CENTER, 30, 20)); oznaka = new JLabel("Broj pritisaka = 0"); add(oznaka); JButton dugme = new JButton("Pritisni me"); add(dugme); // Dodavanje objekta za rukovanje doga dajem dugmeta kao // instance anonimne klase koja implementira ActionListener dugme.addActionListener(new ActionListener() { private int broja c; // broja c pritisaka na dugme public void actionPerformed(ActionEvent e) { broja c++; oznaka.setText("Broj pritisaka = " + broja c); } }); } }

G LAVA 7

P ROGRAMSKE GREKE

Do sada smo govorili o mogu cnostima programskog jezika Java za pisanje ispravnih programa. Programiranje je intelektualna aktivnost koja je, naalost, veoma podlona grekama i sasvim mala nepreciznost u programu moe dovesti do prevremenog prekida izvravanja programa ili do, jo gore, normalnog zavretka programa, ali sa potpuno pogrenim rezultatima. Posledice loih programa mogu biti manje ozbiljne (npr. izgubljeno vreme i nerviranje u obi cnim primenama jer se program uko cio ili puca), vrlo ozbiljne (npr. izgubljen novac i ugled u bankarskim i telekomunikacionim primenama), pa c ak i katastrofalne (npr. gubitak ljudskih ivota u medicinskim i vazduhoplovnim primenama).

7.1 Ispravni i robustni programi


Ispravnost programa je najvaniji uslov koji svi programi moraju da ispunjavaju, jer je, naravno, besmisleno da programi ne obavljaju zadatak za koji su predvideni. Ali skoro isto tako vaan uslov za dobre programe je da budu robustni. To zna ci da oni u neo cekivanim okolnostima tokom svog izvravanja treba da reaguju na razuman na cin. Razmotrimo konkretnije jedno programsko reenje problema sortiranja, odnosno program koji treba da u cita neki niz brojeva od korisnika i da prikae te iste brojeve u rastu cem redosledu. Taj program je ispravan ukoliko svoj zadatak obavlja za bilo koji niz brojeva koje korisnik unese. Ovaj program je robustan ukoliko se razumno postupa sa potencijalnim korisnikovim grekama. Na primer, kada korisnik pogreno unese nenumeri cku vrednost za neki ulazni broj, program treba da prikae odgovaraju cu poruku o greci i da prekine svoje izvravanje na kontrolisan na cin. Treba naglasiti da, iako svaki program mora biti apsolutno ispravan, svaki program ne mora biti potpuno robustan. Na primer, neki mali pomo cni pro-

208

7. P ROGRAMSKE GREKE

gram koji smo napisali isklju civo za svoje potrebe ne mora biti toliko robustan kao neki komercijalni program koji se koristi u poslovne svrhe. O cigledno je da nema svrhe ulagati dodatni trud u pove canje robustnosti pomo cnog programa koji jedino sami koristimo, jer verovatno ne cemo napraviti greku prilikom njegovog kori cenja. A ukoliko se to nenamerno i dogodi, poto dobro poznajemo svoj program, zna cemo kako da reagujemo na greku pri njegovom izvravanju. S druge strane, izvravanje komercijalnog programa nije pod kontrolom programera i takav program mogu koristiti osobe razli citog nivoa ra cunarske pismenosti. Zbog toga se za izvravanje komercijalnog programa mora pretpostaviti minimalisti cko okruenje i moraju se predvideti sve vanredne okolnosti kako bi i najmanje sosticirani korisnici mogli pravilno reagovati na greke. U Javi je od po cetka prepoznata vanost ispravnih i robustnih programa, pa su u sam jezik ugradeni mehanizmi koji naj ce ce greke programera svode na najmanju meru. To je posebno obezbedeno slede cim svojstvima program skog jezika Java: Sve promenljive moraju biti deklarisane. U (starijim) programskim jezicima kod kojih promenljive ne moraju da se deklariu, promenljive se uvode kada se prvi put koriste u programu. Iako ovaj na cin rada sa promenljivim izgleda pogodniji za programere, on je c est uzrok teko prepoznatljivih greaka kada se nenamerno pogreno napie ime neke promenljive. To je isto kao da se uvodi potpuno razli cita promenljiva, to moe izazvati neo cekivane rezultate c iji uzrok nije lako otkriti. Kada sve promenljive moraju unapred biti deklarisane, kompajler moe prilikom prevodenja programa da otkrije da pogreno napisana promen ljiva nije deklarisana i da ukae na nju. To onda zna ci da programer lako moe ispraviti takvu greku. Indeksi elemenata niza u Javi se proveravaju da li lee u dozvoljenim granicama. Nizovi se sastoje od elemenata koji su numerisani od nule do nekog maksimalnog indeksa. Svako kori cenje elementa niza c iji je indeks van ovog intervala bi ce otkriveno od strane Java interpretatora (JVM) i izvravanje programa bi ce nasilno prekinuto, ukoliko se na neki drugi na cin ne postupi u programu. U drugim jezicima (kao to su C i C++) prekora cenje granica niza se ne proverava i zato je u tim jezicima mogu ce da se, recimo, upie neka vrednost u memorijsku lokaciju koja ne pripada nizu. Kako ta lokacija slui za potpuno druge svrhe, posledice upotrebe nepostoje ceg elementa niza su vrlo nepredvidljive. Ova programska graka se popularno naziva prekora cenje bafera (engl. buffer overow) i moe se iskoristiti u maliciozne svrhe u raznim

7.1. Ispravni i robustni programi

209

virusima. Greke ovog tipa nisu dakle mogu ce u Java programima, jer je nemogu c nekontrolisan pristup memorijskim lokacijama koje ne pripadaju nizu. Direktno manipulisanje pokaziva cima u Javi nije dozvoljeno. U drugim jezicima je u sutini mogu ce obezbediti da pokaziva cka promenljiva ukazuje na proizvoljnu memorijsku lokaciju. Ova mogu cnost s jedne strane daje veliku mo c programerima, ali je s druge strane vrlo opasna, jer ukoliko se pokaziva ci koriste na pogrean na cin to dovodi do nepredvidljivih rezultata. U Javi neka promenljiva klasnog tipa (tj. pokaziva cka promenljiva) ili moe ukazivati na valjani objekat u memoriji ili takva promenljiva moe sadrati specijalnu vrednost null. Svako kori cenje pokaziva ca c ija je vrednost null bi ce otkrivena od strane Java interpretatora i izvravanje programa bi ce nasilno prekinuto, ukoliko se na neki drugi na cin ne postupi u programu. A ukoliko pokaziva c ukazuje na ispravnu memorijsku lokaciju, poto aritmeti cke operacije sa pokaziva cima nisu dozvoljene, u Javi je nemogu ce nekontrolisano pristupiti pogrenim delovima programske memorije. U Javi se automatski oslobada memorija koju zauzimaju objekti koji nisu vie potrebni u programu. U drugim jezicima su sami programeri odgovorni za oslobadanje memorije koju zauzimaju nepotrebni objekti. Ukoliko to ne u cine (iz nemarnosti ili iz neznanja), nepotrebno zauzeta memorija moe se brzo uve cati do nivoa kada je dalje izvravanje samog programa (ili drugih programa u ra cunaru) nemogu ce, jer u rac unaru nema vie slobodne glavne memorije. Ova programska graka se popularno naziva curenje memorije (engl. memory leak) i u Javi se mnogo tee mogu nenamerno izazvati greke ovog tipa. Svi potencijalni izvori greaka programera ipak nisu mogli biti potpuno eliminisani u Javi, jer bi njihovo otkrivanje znatno usporilo izvravanje programa. Jedna oblast gde su greke mogu ce su numeri cka izra cunavanja koja ne podleu nikakvim proverama. Neki primeri takvih greaka u Javi su: Kod prekora cenja opsega za cele brojeve mogu se dobiti neo cekivani rezultati: na primer, zbir 2147483647 + 1 dva broja tipa int kao rezultat daje najmanji negativan broj 2147483648 tipa int. Nedenisane vrednosti realnih izraza kao to su deljenje s nulom i 4, kao i rezultati prekora cenja opsega brojeva za tipove float i double, predstavljeni su specijalnim konstantama Double.POSTIVE_INFINITY, Double.NEGATIVE_INFINITY i Double.NaN.

210

7. P ROGRAMSKE GREKE

Zbog priblinog predstavljanja realnih brojeva (na primer, realan broj 1/3 = 0.333 sa beskona cno decimala zaokruuje se na broj 0.3333333 sa 7 decimala tipa float ili na broj 0.333333333333333 sa 15 decimala tipa double), greke usled zaokruivanja realnih brojeva mogu se akumulirati i dovesti do neta cnih rezultata. Robustni programi, kao to je ve c spomenuto, moraju preiveti vanredne okolnosti tokom svog izvravanja. Za pisanje takvih programa moe se primeniti opti pristup koji nije speci can za Javu i koji se sastoji u tome da se predvide sve izuzetne okolnosti u programu i uklju ce provere za svaki potencijalni problem. Na primer, ako se u programu koriste elementi niza a , onda se u Javi proveravaju njihovi indeksi da li lee u dozvoljenim granicama i ukoliko to nije slu caj, izvravanje programa se nasilno prekida. Medutim, da bismo takve greke sami otkrili i kontrolisali, u programu moemo postupiti na slede ci na cin:
if (i < 0 || i > a.length) { // Obrada greke nedozvoljenog indeksa i System.out.println("GREKA: pogrean indeks " + i + " niza a"); System.exit(0); } else { . . // Normalna obrada elementa a[i] . }

U ovom primeru se prikazuje vrednost indeksa niza a koji lei u nedozvoljenim granicama i prekida se ceo program. Ponekad takvo postupanje sa grekom nije odgovaraju ce, jer je moda u nekom drugom delu programa mogu c oporavak od greke na manje brutalan na cin. Tako, u metodu u kojem se koriste elementi niza a moda treba samo vratiti indikaciju da je dolo do greke. Prethodni programski fragment zato postaje:
if (i < 0 || i > a.length) { // Obrada greke nedozvoljenog indeksa i return -1; } else { . . // Normalna obrada elementa a[i] . }

U ovom primeru se specijalna vrednost 1 vra ca kao indikacija da je dolo do greke i preputa se pozivaju cem metodu da proveri ovaj rezultat i da

7.2. Izuzeci

211

shodno tome postupi na na cin koji je moda prikladniji od prostog prekida izvravanja. Glavni nedostaci opteg pristupa za predupredivanje potencijalnih gre aka su: Teko je predvideti sve potencijalne probleme u programu, a nekad je to i nemogu ce. Nije uvek jasno kako treba postupati u slu caju nekog problema. Program postaje komplikovan splet pravih naredbi i if naredbi. Na taj na cin logika c ak i jednostavnih programa postaje teko razumljiva, a to dalje izaziva mnogo ozbiljnije teko ce kod menjanja programa. U Javi je uzeta u obzir manjkavost ovog ad-hok pristupa kod pisanje robustnih programa na drugim jezicima, pa je predviden poseban mehanizam koji obezbeduje postupanje s grekama u programu na vie strukturiran na c in. Taj mehanizam se zasniva na konceptu izuzetaka i naziva se rukovanje izuzecima (engl. exception handling ).

7.2 Izuzeci
Izuzetak (engl. exception) je zapravo skra ceni termin za izuzetnu okolnost koja se moe desiti tokom izvravanja programa. Izuzeci su optiji pojam od programskih greaka i obuhvataju sve vanredne dogadaje koji program mogu skrenuti sa normalnog toka izvravanja. Tu spadaju recimo i hardverske greke na koje programer nema uticaja. Terminologija u vezi sa izuzecima je prili cno kolokvijalna, pa se tako kae da je izuzetak izba cen kada se izuzetna okolnost desi tokom izvravanja programa i izbaci ga iz koloseka. Ukoliko se u programu ne postupi sa izuzetkom na neki na cin kada se izuzetak desi, program se momentalno prekida i zavrava se njegovo izvravanje. Postupak rukovanja izuzetkom u programu sastoji se od dve aktivnosti: izuzetak se najpre hvata i zatim se izuzetak obraduje . Opti koncept izuzetaka je u Javi naravno prilagoden objektno orijentisanoj metodologiji. To zna ci da se izuzeci predstavljaju objektima odredenih klasa koji se konstruiu u trenutku nastanka izuzetne okolnosti tokom izvravanja programa. Ovi objekti izuzetaka sadre opis uzroka koji je doveo do izbacivanja izuzetka, listu metoda koji su bili aktivni kada se izuzetak dogodio (engl. method call stack) i sli cne blie informacije o stanju programa u trenutku prekida njegovog izvravanja.

212

7. P ROGRAMSKE GREKE

Svi objekti izuzetka moraju pripadati nekoj klasi koja je naslednica standardne klase Throwable iz paketa java.lang. Ova klasa se nalazi na vrhu sloene hijerarhije klasa kojima su predstavljeni razli citi tipovi izuzetaka. Na slici 7.1 je prikazan samo mali deo ove hijerarhije sa nekim karakteristi cnim klasama.
Throwable

Error

Exception

RuntimeException

InterruptedException

IOException

EOFException

FileNotFoundException

IllegalArgumentException

ArrayIndexOutOfBoundsException

Slika 7.1: Deo hijerarhije klasa izuzetaka.

Kao to je prikazano na slici 7.1, klasa Throwable ima dve direktne klase naslednice, Error i Exception. Ove dve klase imaju svoje unapred denisane mnoge druge klase naslednice. Pored toga, ukoliko postoje ce klase nisu odgovaraju ce za konkretnu primenu, u programu se mogu denisati nove klase izuzetaka radi predstavljanja posebnih tipova greaka. Klasa Error i njene naslednice predstavljaju vrlo ozbiljne greke unutar samog Java interpretatora. To su obi cno fatalne greke koje treba da izazovu prekid izvravanja programa poto ne postoji razuman na cin da se program oporavi od tih greaka. Na primer, izuzetak tipa ClassFormatError deava se kada Java interpretator otkrije neispravan format bajtkoda u datoteci koja sadri prevedenu Java klasu. Ako je takva klasa deo programa koji se izvrava, onda sigurno nema smisla da se nastavi dalje izvravanje tog programa. U optem slu caju dakle, izuzeci tipa (ili podtipa od) Error nisu predvideni da budu uhva ceni i obradeni u programu, nego samo da izazovu prevremen prekid programa. S druge strane, izuzeci tipa (ili podtipa od) Exception jesu izuzeci koji su predvideni da budu uhva ceni i obradeni u programu na od govaraju ci na cin, kako ne bi izazvali nasilan prekid programa. Ovi izuzeci u mnogim slu cajevima predstavljaju greke u programu ili ulaznim podacima

7.2. Izuzeci

213

koje se mogu o cekivati i od kojih se program moe oporaviti na razuman na cin. Medu naslednicama klase Exception je klasa RuntimeException koja opisuje naj ce ce greke koje se mogu desiti tokom izvravanja programa. Tu spadaju greke usled nedozvoljenog argumenta metoda, prekora cenja granice niza, kori cenja nepostoje ce reference i tako dalje. Optije, izuzeci tipa RuntimeException predstavljaju programske greke koje su posledica propusta programera i zato ih treba ispraviti u programu. Izuzeci tipa RuntimeException i Error (i njihovih podtipova) imaju zajedni cku osobinu da se mogu zanemariti u programu u smislu da se svesno prenebregava mogu cnost da c e se program nasilno prekinuti ukoliko se takvi izuzeci dese tokom izvravanja. To zna ci da njihovo hvatanje i obrada u programu nije obavezna, ve c se programeru ostavlja izbor da li je bolje u programu reagovati na ove izuzetke ili prosto dozvoliti da se izvravanje programa nasilno prekine. S druge strane, zajedni cka osobina izuzetaka klase Exception i svih njenih klasa naslednica (osim RuntimeException) jeste to da se tim izuzecima mora rukovati u programu. To zna ci da ukoliko u programu postoji mogu cnost njihovog izbacivanja, oni se u programu moraju hvatati i obraditi na na cin o kome c emo govoriti u nastavku. Neke od klasa izuzetaka c ije je rukovanje u programu obavezno prikazane su na slici 7.1 sivim bojom. Klasa Throwable sadri nekoliko objektnih metoda koji se mogu koristiti sa svakim objektom izuzetka. Na primer, ako je e promenljiva tipa Throwable koja sadri referencu na neki objekat izuzetka, onda: e.getMessage() kao rezultat daje tekstualni opis greke koja je dovela do izbacivanja izuzetka. e.printStackTrace() prikazuje listu aktivnih metoda u trenutku izbacivanja izuzetka. e.toString() kao rezultat daje reprezentaciju objekta izuzetka u obliku stringa.

Naredba try
Za rukovanje izuzecima u programu, odnosno za njihovo hvatanje i obradu, koristi se posebna naredba try. Nepotpun oblik ove naredbe sastoji se od dva bloka, try i catch, koji se piu jedan iza drugog:
try { . . // Naredbe koje mogu izazvati izbacivanje izuzetaka

214

7. P ROGRAMSKE GREKE

. . . } catch (tip-izuzetka promenljiva) { . . // Naredbe koje obra duju izuzetak tipa tip-izuzetka . }

Pojednostavljen opis izvravanja naredbe try moe se podeliti u c etiri koraka: 1. Izvravaju se naredbe u telu try bloka. 2. Ako se tokom izvravanja ovih naredbi ne izbaci nijedan izuzetak ili se izbaci izuzetak razli citog tipa od onog navedenog u zagradama iza re ci catch, izvravanje tela catch bloka se preska ce. 3. Ako se tokom izvravanja ovih naredbi izbaci izuzetak tipa navedenog u zagradama iza re ci catch, izvrava se telo catch bloka. 4. Izvravanje programa se nastavlja iza tela catch bloka, ako druga cije nije reeno. Na primer, za inicijalizovanje i -tog elementa niza a primenom naredbe try moe se napisati:
try { a[i] = 0; } catch (ArrayIndexOutOfBoundsException e) { System.out.println("Indeks " + i + " niza a je izvan granica"); e.printStackTrace(); }

U ovom try-catch bloku se najpre pokuava izvravanje bloka naredbi iza klauzule try. Kako se taj blok ovde sastoji samo od jedne naredbe, to zna ci da se najpre pokuava dodeljivanje vrednosti 0 elementu niza a sa indeksom i . Ako se ta naredba dodele uspeno izvri, odnosno pri njenom izvravanju se ne izbaci nijedan izuzetak, izvravanje catch bloka se prosto preska ce i izvravanje programa se normalno nastavlja od mesta neposredno iz catch bloka. Ako se pak naredba dodele u try bloku ne moe uspeno izvriti usled nepravilnog indeksa niza, izbacuje se izuetak tipa
ArrayIndexOutOfBoundsException

koji se hvata catch blokom i izvravaju se naredbe i telu tog bloka. Ta cnije, pre izvravana tela catch bloka konstruie se objekat koji predstavlja izba ceni izuzetak i njegova referenca se dodeljuje promenljivoj e koja se koristi u telu

7.2. Izuzeci

215

catch bloka. Drugim re cima, ovde c e se izvravanjem catch bloka najpre pri-

kazati poruka o greci prekora cenja granica niza a i zatim c e se prikazati lista aktivnih metoda u trenutku izbacivanja izuzetka. Nakon toga se izvravanje programa nastavlja od mesta neposredno iza catch bloka. U optem slu caju, viti caste zagrade u try i catch blokovima su obavezne i ne mogu se izostaviti iako sadre samo jednu naredbu. Telo catch bloka se naziva rukovalac izuzetka (engl. exception handler ) odgovaraju ceg tipa, jer je to deo programa gde se postupa sa izuzetkom onog tipa koji je naveden u zagradama iza slubene re ci catch. Primetimo da se u prethodnom primeru hvata i obraduje samo izuze tak izba cen usled prekora cenja granice niza, mada generalno jedan try blok moe imati vie pridruenih catch blokova kako bi se moglo rukovati izuzecima vie razli citih tipova koji se mogu izbaciti u jednom try bloku. Tako se prethodni primer moe proiriti, recimo, na slede ci na cin:
try { a[i] = 0; } catch (ArrayIndexOutOfBoundsException e) { System.out.println("Indeks " + i + " niza a je izvan granica"); e.printStackTrace(); catch (NullPointerException e) { System.out.println("Niz a ne postoji"); e.printStackTrace(); }

U ovom fragmentu se prilikom izvravanja naredbe dodele


a[i] = 0

hvataju i obraduju dve vrste greaka: prekora cenje granica niza i kori cenje nepostoje ce reference. Ukoliko se ne desi nijedna greka u try bloku, oba catch bloka se preska cu. Ako se izbaci izuzetak usled prekora cenja granica niza, izvrava se samo prvi catch blok, a drugi se preska ce. Ako se izbaci izuzetak usled kori cenja nepostoje ce reference, izvrava se samo drugi catch blok, a prvi se preska ce. Obe klase ArrayIndexOutOfBoundsException i NullPointerException su naslednice klase RuntimeException, pa se prethodni primer moe kra ce zapisati na slede ci na cin:
try { a[i] = 0; } catch (RuntimeException e) { System.out.print("Greka " + e.getMessage()); System.out.println(" u radu sa nizom a");

216

7. P ROGRAMSKE GREKE

e.printStackTrace(); }

U ovom fragmentu se klauzulom catch hvataju svi izuzeci koji pripadaju klasi RuntimeException ili bilo kojoj od njenih klasa naslednica. Ovaj princip vai u optem slu caju i pokazuje zato su klase izuzetaka organizovane u hijerarhiju klasa. Naime, to obezbeduje ve cu eksibilnost u rukovanju izuzecima, jer programeri imaju mogu cnost da u posebnim slu cajevima hvataju speci cne tipove izuzetaka, a i da hvataju mnogo ire tipove izuzetaka ukoliko nije bitan njihov poseban tip. Primetimo da je zbog ovoga mogu ce da izba ceni izuzetak odgovara ve cem broju rukovalaca, ako je vie catch blokova pridrueno jednom try bloku. Na primer, izuzetak tipa NullPointerException bi se uhvatio klauzulama catch za tipove NullPointerException, Runtimecaju, izvrava se samo prvi Exception, Exception ili Throwable. U tom slu catch blok u kojem se hvata izuzetak. Uzgred, mada prethodni primeri nisu mnogo realisti cni, oni pokazuju zato rukovanje izuzecima tipa RuntimeException nije obavezno u programu. Jednostavno, mnogo greaka ovog tipa se moe desiti tokom izvravanja programa, pa bi svakako bilo frustriraju ce da se mora pisati try-catch blok svaki put kada se, recimo, koristi neki niz u programu. Pored toga, za spre cavanje programskih greaka ovog tipa, kao to su one usled prekora cenja granica niza ili kori cenja nepostoje ce reference, bolje je primeniti paljivo programiranje nego rukovanje izuzecima.

Klauzula finaly
Dosadanji opis naredbe try nije potpun, jer ta naredba u Javi moe imati dodatnu klauzulu finaly. Razlog za ovu mogu cnost je taj to u nekim sluc ajevima kada se dogodi greka prilikom izvravanja programa, potrebno je uraditi neki postupak kojim se stanje programa mora vratiti na konzistentno stanje pre pojave greke. Tipi can primer za ovo su situacije u programu kada su zauzeti neki ra cunarski resursi koji se moraju osloboditi nakom otkrivanja greke. Ukoliko se, na primer, prilikom c itanja ili pisanja neke datoteke na disku dogodi ulazno/izlazna greka zbog koje se ne moe nastaviti rad sa datotekom, ova datoteka se mora zatvoriti kako bi se oslobodili ra cunarski resursi koji su u programu rezervisani za rad sa datotekom. U ovakvim slu cajeva se try-catch bloku u Javi moe dodati jo jedan blok u kome se izvravaju naredbe bez obzira na to da li se greka u try bloku dogodila ili ne. Taj blok naredbi nije obavezan i navodi se na kraju try-catch bloka iza slubene re ci finaly. Prema tome, najoptiji oblik naredbe try ima slede ci izgled:

7.2. Izuzeci

217

try { . . // Naredbe koje mogu izazvati izbacivanje izuzetaka . } catch ( ... ) { . . // Naredbe koje obra duju izuzetak odre denog tipa . } . . // Ostali catch blokovi . finally { . . // Naredbe koje se uvek izvravaju . }

Ako je klauzula finaly pridruena try-catch bloku, finaly blok se uvek izvrava prilikom izvravanja try-catch bloka, bez obzira na to da li je izuzetak izba cen u try bloku ili da li je izba ceni izuzetak uhva cen i obraden u catch bloku. Preciznije, ako nije izba cen nijedan izuzetak u try bloku, finaly blok se izvrava nakon poslednje naredbe try bloka; ako je izba cen neki izuzetak u try bloku, finaly blok se izvrava nakon izvravanja odgovaraju ceg catch bloka ili odmah ukoliko izuzetak nije uhva cen nijednim catch blokom. Cest primer oslobadanja zauzetih resursa sre ce se kod mrenog progra miranja kada je potrebno na po cetku otvoriti mrenu konekciju radi komuniciranja sa udaljanim ra cunarom i na kraju zatvoriti tu konekciju. Programski detalji ovih aktivnosti nisu vani, ali robustan na cin rada programa preko jedne mrene konekcije moe se logi cki predstaviti na slede ci na cin:
try { // Otvoriti konekciju (i zauzeti ra cunarske resurse) } catch (IOException e) { // Prijaviti greku da uspostavljanje veze nije uspelo return; // dalji rad je nemogu c } // U ovoj ta cki je konekcija sigurno otvorena try { // Normalan rad preko otvorene konekcije } catch (IOException e) { // Prijaviti ulazno/izlaznu greku (dalji rad je nemogu c) }

218

7. P ROGRAMSKE GREKE

finally { // Zatvoriti konekciju (i osloboditi resurse) }

U ovom fragmentu se prvom naredbom try obezbeduje to da mrena konekcija bude svakako otvorena kada se komunicira sa udaljenim ra cunarom u nastavku. Klauzulom finaly u drugoj naredbi try obezbeduje se to da mrena konekcija sigurno bude zatvorena, bez obzira na to da li je dolo do prekida veze ili ne u toku normalnog rada preko otvorene konekcije. Radi potpunosti opisa naredbe try, napomenimo jo da u njoj nisu ni klauzule catch obavezne. Preciznije, naredba try mora se pisati bilo s jednom ili vie klauzula catch, ili samo s jednom klauzulom finaly ili sa obe od ovih mogu cnosti.

Programsko izbacivanje izuzetka


Ponekad sama logika programa moe ukazivati da neka grana izvravanja dovodi do izuzetne okolnosti (greke), ali se na tu okolnost ne moe na razuman na cin reagovati tamo gde se to logi cki otkriva. U Javi se na takvom mestu moe programski izbaciti izuzetak s ciljem da taj izuzetak bude obraden u nekom drugom delu programa u kojem se to moe smislenije uraditi. Programsko izbacivanje izuzetka postie se naredbom throw koja ima opti oblik:
throw objekat-izuzetka

Ovde objekat-izuzetka mora pripadati klasi Throwable ili nekoj njenoj klasi naslednici. (Obi cno je to klasa naslednica od Exception.) Na primer, ukoliko se u nekoj ta cki programa otkrije da dolazi do deljenja s nulom, moe se izbaciti izuzetak ukoliko nije jasno da li na tom mestu treba potpuno prekinuti izvravanje ili prekinuti samo aktuelni metod ili neto tre ce:
throw new ArithmeticException("Deljenje s nulom")

Da bi se ukazalo da u telu nekog metoda moe do ci do izbacivanja izuzetka, zaglavlju metoda se moe dodati klauzula throws.1 Na primer, ukoliko prilikom izvravanja metoda nekiMetod() moe do ci do izbacivanja izuzetka tipa ArithmeticException, to se moe obelodaniti na slede ci na cin:
public void nekiMetod( ... ) throws ArithmeticException { . . . }
1 Obratite panju na razliku izmedu ci throw i throws prva se koristi za naredbu, a re druga za klauzulu u zaglavlju metoda.

7.2. Izuzeci

219

Ukoliko u metodu moe do ci do izbacivanja izuzetka vie tipova, ovi tipovi se navode iza klauzule throws razdvojeni zapetama. Mogu cnost programskog izbacivanja izuzetaka naredbom throw korisna je naro cito za pisanje metoda opte namene koji se mogu koristiti u razli citim programima. U tom slu caju, autor metoda ne moe na razuman na cin postupiti sa potencijalnom grekom, jer ne zna ta cno kako c e se metod koristiti. Naime, ukoliko se samo prikae poruka o greci i nastavi izvravanje metoda, to obi cno dalje izaziva ozbiljnije greke. Prikazivanje poruke o greci i prekid izvravanja programa je skoro isto tako neprihvatljivo, jer se moda u nekom drugom delu programa greka moe na zadovoljavaju ci na cin amortizovati. Metod u kome se dogodila greka mora na neki na cin o tome obavestiti drugi metod koji ga poziva. U programskim jezicima koji nemaju mehanizam izuzetaka, prakti cno jedini na cin za ovo je vra canje neke specijalne vrednosti kojom se ukazuje da metod nije normalno zavrio svoj rad. Medutim, ovo ima smisla samo ako se u jednom metodu nakon svakog poziva drugog metoda proverava njegova vra cena vrednost. Kako je ove provere c esto vrlo lako prevideti, izuzeci su ekasnije sredstvo prinude za programere da obrate panju na potencijalne greke.

Primer: koreni kvadratne jedna cine


Da bismo prethodnu diskusiju potkrepili jednim prakti cnim primerom, pretpostavimo da treba napisati metod kojim se reava kvadratna jedna cina opteg oblika ax 2 +bx +c = 0. Drugim re cima, za date konstante a , b, c , traeni metod kao rezultat treba da vrati realne korene x 1 i x 2 odgovaraju ce kvadratne jedna cine. Ukoliko radi jednostavnosti pretpostavimo da se x 1 i x 2 vra caju preko dva javna polja objekta tipa Koren, jedno robustno reenje u kojem se otkrivaju sve potencijalne greke jeste slede ci metod.
public Koren kvadratnaJedna cina(double a, double b, double c) throws IllegalArgumentException { if (a == 0) throw new IllegalArgumentException("Nije kvadratna jedna cina") else if (b*b-4*a*c < 0) throw new IllegalArgumentException("Nisu realni koreni") else { Koren k = new Koren(); k.x1 = -b + Math.sqrt(b*b-4*a*c)/(2*a); k.x2 = -b - Math.sqrt(b*b-4*a*c)/(2*a); return k; } }

220

7. P ROGRAMSKE GREKE

Primetimo da u ovom metodu nije unapred jasno kako treba postupiti u slu cajevima kada se realna reenja kvadratne jedna cine ne mogu izra cunati. Naime, da li treba odmah prekinuti izvravanje, ili pre toga treba prikazati poruku o greci na ekranu, ili treba samo vratiti indikator o greci, ili treba uraditi neto drugo? Zbog toga se u metodu koristi mogu cnost programskog izbacivanja izuzetka u nadi da c e izba ceni izuzetak obraditi neki rukovalac izuzecima u metodu koji poziva metod kvadratnaJedna cina().

7.3 Postupanje sa izuzecima


Usled neke vanredne okolnosti prilikom izvravanja programa, mogu ci izvori izbacivanja izuzetka su: Java interpretator (JVM) pod c ijom se kontrolom izvrava program; ili sam program kada se izvrava naredba throw u nekom metodu. Nezavisno od izvora, nakon izbacivanja izuzetka u jednom delu programa moe do ci do propagiranja izuzetka i njegovog hvatanja u potpuno drugom delu programa. U Javi se sa izba cenim izuzetkom u oba prethodna slu caja postupa na isti na cin o kome se detaljnije govori u nastavku. Pretpostavimo da metod A poziva metod B i da se u telu metoda B izbacuje izuzetak prilikom izvravanja neke naredbe. Drugim re cima, denicije ovih metoda imaju otprilike slede ci oblik:
void A( ... ) { . . . B( ... ) // poziv metoda B . . . } void B( ... ) { . . . // naredba koja je uzrok izuzetka E!E . . . }

Mehanizam rukovanja izuzetkom koji je izbe cen u metodu B zasniva se najpre na tome da li je izuzetak izba cen unutar nekog try-catch bloka. Ako je to slu caj, struktura metoda B ima otprilike slede ci oblik:
void B( ... ) { . . . try { . . .

7.3. Postupanje sa izuzecima

221

E!E

// naredba koja je uzrok izuzetka

. . .

} catch ( ... ) { . . . } finally { . . . } . . . }

Ako je dakle izuzetak izba cen unutar nekog try-catch bloka u metodu B, postupanje sa izuzetkom dalje zavisi od mogu ca dva slu caja: 1. Izuzetak je uhva cen nekim catch blokom. U ovom slu caju, a) izvrava se odgovaraju ci catch blok; b) izvrava se finally blok ukoliko je naveden; c) nastavlja se normalno izvravanje metoda B iza finally bloka (ili iza catch bloka ukoliko finally blok nije naveden). 2. Izuzetak nije uhva cen nijednim catch blokom. Onda se sa izuzetkom postupa na isti na cin kao da je izuzetak izba cen izvan nekog try-catch bloka, o c emu se govori u nastavku. Primetimo da se zbog ovog drugog slu caja, opis celokupnog mehanizma rukovanja izuzecima moe pojednostaviti. Naime, prethodna dva slu caja se kra ce razlikuju tako to se kae da je izba ceni izuzetak uhva cen ili nije uhvac en. U prvom slu caju se dakle podrazumeva da je izuzetak izba cen unutar nekog try bloka i uhva cen i obraden cem catch bloku. Drugi u odgovaraju slu caj podrazumeva da je ili izuzetak izba cen unutar nekog try bloka, ali nije uhva cen nijednim pridruenim catch blokom, ili je izuzetak izba cen izvan bilo kog try bloka (i naravno nije uhva cen nijednim catch blokom). U drugom slu caju kada izba ceni izuzetak nije uhva cen u metodu B, izvravanje tog metoda se momentalno prekida i metod A koji je pozvao metod B dobija ansu da obradi izba ceni izuzetak. Pri tome se smatra da je naredba poziva metoda B u metodu A ona koja je uzrok izbacivanja izuzetka u metodu A. Za rukovanje originalnim izuzetkom u metodu A primenjuje se isti postupak koji je primenjen na prvobitnom mestu izbacivanja izuzetka u metodu B. Naime, naredba poziva metoda B u metodu A moe se nalaziti unutar nekog try-catch bloka ili ne. U slu caju da se ona nalazi i da je originalni izuzetak uhva cen i obraden, postupanje s njim se zavrava i nastavlja se normalno izvravanje metoda A iza try-catch bloka.

222

7. P ROGRAMSKE GREKE

U slu caju da originalni izuzetak nije uhva cen u metodu A, izvravanje tog metoda se prekida kod naredbe poziva metoda B i metod koji je pozvao metod A dobija ansu da obradi originalni izuzetak. Optije, lanac poziva metoda se odmotava i pri tome svaki metod dobija ansu da obradi originalni izuzetak ukoliko to nije uradeno u prethodnom metodu. Prema tome, postupanje sa izba cenim originalnim izuzetkom se nastavlja na isti na cin tokom vra canja du lanca poziva metoda, sve dok se pri tome izuzetak ne obradi u nekom metodu ili dok se ne dode cetnog metoda main(). U i prekine izvravanje po ovom drugom slu caju Java interpretator (JVM) prekida izvravanje programa i prikazuje standardnu poruku o greci.

Obavezno i neobavezno rukovanje izuzecima


U Javi su izuzeci generalno podeljeni u dve kategorije prema tome da li programeri u svojim programima moraju obratiti manju ili ve cu panju na mogu cnost izbacivanja izuzetaka. To prakti cno zna ci da jednu kategoriju izuzetaka c ine oni c ije rukovanje nije obavezno, dok drugu c ine oni c ije rukovanje jeste obavezno u programima u kojima se mogu desiti. Izuzeci c ije rukovanje nije obavezno u programima nazivaju se neproveravani izuzeci (engl. unchecked exceptions), a izuzeci c ije rukovanje jeste obavezno u programima nazivaju se proveravani izuzeci (engl. checked exceptions). Ova, pomalo zbunjuju ca, terminologija zapravo govori da li Java kompajler prilikom prevodenja programa proverava postojanje rukovalaca odgo varaju ce vrste izuzetaka za neproveravane izuzetke ne proverava, dok za proveravane izuzetke proverava postojanje njihovih rukovalaca u programu. U neproveravane izuzetke spadaju izuzeci koji pripadaju klasama Error,
RuntimeException ili nekim njihovim klasama naslednicama. Osnovno svoj-

stvo neproveravanih izuzetaka je dakle da ih programeri mogu (ali ne moraju) zanemariti u svojim programima, jer kompajler ne proverava da li program sadri rukovaoce ovih izuzetaka. Drugim re cima, za neproveravane izuzetke se u programu ne moraju (ali mogu) pisati rukovaoci tim izuzecima, niti se moraju (ali mogu) pisati klauzule throws u zaglavlju metoda u kojima moe do ci do izbacivanja neproveravanih izuzetaka. Na primer, budu ci da je klasa IllegalArgumentException izvedena od RuntimeException, izuzeci usled nepravilnog argumenta metoda pripadaju kategoriji neproveravanih izuzetaka. Zato se u metodu za izra cunavanje korena kvadratne jedna cine na strani 219 ne mora pisati klauzula throws u zaglavlju tog metoda (ali moe, kao to je to tamo navedeno radi jasno ce). Isto tako, greke usled prekora cenja granica nekog niza su neproveravani izuzeci,

7.3. Postupanje sa izuzecima

223

pa se manipulisanje elementima nizova u programu ne mora obavljati unutar try-catch blokova. Proveravani izuzeci su oni koji pripadaju klasi Exception i njenim naslednicama (osim klase RuntimeException). U proveravane izuzetke spadaju greke c ija je priroda nastanka izvan kontrole programera (recimo, to su ulaznoizlazne greke), odnosno to su greke koje se potencijalno ne mogu izbe ci, ali ih robustan program mora na neki na cin obraditi. Da bi se zato program u kome se mogu desiti takve greke u cinio robustnijim, kompajler proverava da li postoje rukovaoci tih greaka i ne dozvoljava uspeno prevodenje programa ukoliko to nije slu caj. Programeri su dakle primorani da obrate panju na proveravane izuzetke i moraju ih u programu obraditi na jedan od dva na cina: 1. Direktno pisanjem rukovaoca proveravanog izuzetka, odnosno navodenjem naredbe koja moe izbaciti proveravani izuzetak unutar try bloka i hvatanjem tog izuzetka u pridruenom catch bloku. 2. Indirektno delegiranjem drugom delu programa da rukuje proveravanim izuzetkom, odnosno pisanjem klauzule throws za proveravani izuzetak u zaglavlju metoda koji sadri naredbu koja moe izbaciti proveravani izuzetak. U prvom od ovih slu cajeva, proveravani izuzetak bi ce uhva cen u metodu u kojem je nastao, pa nijedan drugi metod koji poziva izvorni metod ne ce ni znati da se desio izuzetak. U drugom slu caju, proveravani izuzetak ne ce biti uhva cen u metodu u kojem je nastao, a kako se taj izuzetak ne moe zanemariti, svaki metod koji poziva izvorni metod mora rukovati originalnim izuzetkom. Zbog toga, ako metod A poziva drugi metod B sa klauzulom throws u svom zaglavlju za proveravani izuzetak, onda se ovaj izuzetak mora obraditi u metodu A opet na jedan od dva na cina: 1. Pisanjem rukovaoca proveravanog izuzetka u metodu A, odnosno navodenjem naredbe poziva metoda B koji je potencijalni izvor proverava nog izuzetka unutar try bloka i hvatanjem tog izuzetka u pridruenom catch bloku. 2. Pisanjem klauzule throws za originalni proveravani izuzetak u zaglavlju pozivaju ceg metoda A radi daljeg delegiranja rukovanja tim izuzetkom. Da bismo ovu diskusiju ilustrovali primerom, razmotrimo postupak otvaranja datoteke na disku koja se c ita u programu. U Javi su ulazne datoteke programa u sistemu datoteka na disku predstavljene (izmedu ostalog) objektima klase FileInputStream iz paketa java.io. Prema deniciji jedne preoptere cene verzije konstruktora ove klase, njegov parametar je ime datoteke

224

7. P ROGRAMSKE GREKE

koja se otvara za c itanje. Ovaj konstruktor dodatno u svom zaglavlju sadri klauzulu throws u obliku:
throws FileNotFoundException

Kako je klasa FileNotFoundException potomak klase Exception (videti sliku 7.1), to zna ci da konstruktor klase FileInputStream izbacuje proveravani izuzetak kada se datoteka s datim imenom ne moe otvoriti za c itanje. Najc e ci uzrok toga je da se traena datoteke ne nalazi na disku, pa otuda dolazi ime klase FileNotFoundException za izuzetke ovog tipa. Znaju ci ovo, kao i to da se proveravani izuzeci ne mogu ignorisati u programu, c itanje neke datoteke na disku moramo reiti na jedan od dva na cina. Prvi na cin je da u metodu citajDatoteku() u kojem se obavlja c itanje datoteke napiemo rukovalac izuzetka tipa FileNotFoundException. Na primer:
void citajDatoteku (String imeDatoteke) { FileInputStream datoteka; . . . try { datoteka = new FileInputStream(imeDatoteke); } catch (FileNotFoundException e) { System.out.println("Datoteka " + imeDatoteke + " ne postoji"); return; } // Naredbe za citanje datoteke . . . }

Drugi na cin je da se rukovanje izuzetkom tipa FileNotFoundException citajDatoteku() za odloi i to prepusti svakom metodu koji koristi metod c itanje datoteke. Na primer:
void citajDatoteku (String imeDatoteke) throws FileNotFoundException { FileInputStream datoteka; . . . datoteka = new FileInputStream(imeDatoteke); // Naredbe za citanje datoteke . . . }

7.4. Denisanje klasa izuzetaka

225

7.4 Denisanje klasa izuzetaka


Izuzeci olakavaju pisanje robustnih programa, jer obezbeduju struktu riran na cin za jasno realizovanje glavne programske logike, ali ujedno i za obradu svih izuzetnih slu cajeva u catch blokovima naredbe try. Java sadri prili can broj unapred denisanih klasa izuzetaka za predstavljanje raznih potencijalnih greaka. Ove standardne klase izuzetaka treba uvek koristiti kada adekvatno opisuju vanredne situacije u programu. Medutim, ukoliko nijedna standardna klasa nije odgovaraju ca za neku po tencijalnu greku, programeri mogu denisati svoje klase izuzetaka radi ta cnijeg predstavljanja te greke. Nova klasa izuzetaka mora biti naslednica klase Throwable ili neke njene naslednice. Generalno, ukoliko se ne eli da se zahteva obavezno rukovanje novim izuzetkom, nova klasa izuzetaka se pie tako da proiruje klasu RuntimeException ili neku njenu naslednicu. Ali ukoliko se eli obavezno rukovanje novim izuzetkom, nova klasa izuzetaka treba da proiruje klasu Exception ili neku njenu naslednicu. U narednom primeru je denisana klasa izuzetaka koja nasleduje klasu Exception i stoga izuzeci tog tipa zahtevaju obavezno rukovanje u programima u kojima se mogu desiti:
public class PogreanPre cnikIzuzetak extends Exception { // Konstruisanje objekta izuzetka // koji sadri datu poruku o greci public PogreanPre cnikIzuzetak(String poruka) { super(poruka); } }

Klasa PogreanPre cnikIzuzetak opisuje greke koje se mogu desiti usled odredivanja negativnog pre cnika za krugove u radu, recimo, programa koji manipulie geometrijskim oblicima. Ova klasa sadri samo konstruktor kojim se konstruie objekat izuzetka sa datom porukom o greci. U konstruktoru se naredbom
super(poruka);

poziva konstruktor nasledene klase Exception kojim se navedena poruka u zagradi prenosi u konstruisani objekat izuzetka. (Klasa Exception sadri konstruktor Exception(String poruka) za konstruisanje objekta izuzetka sa navedenom porukom.) Klasa PogreanPre cnikIzuzetak nasleduje metod getMessage() od na sledene klase Exception (kao uostalom i ostale metode i polja). Tako, ako

226

7. P ROGRAMSKE GREKE

promenljiva e ukazuje na neki objekat tipa PogreanPre cnikIzuzetak, onda se pozivom e.getMessage() moe dobiti poruka o greci koja je navedena u konstruktoru klase PogreanPre cnikIzuzetak. Ali glavna poenta klase PogreanPre cnikIzuzetak je prosto to to ona postoji c inom izbacivanja objekta tipa PogreanPre cnikIzuzetak ukazuje se da se desila greka u programu usled pogrenog pre cnika kruga. Ovo se postie naredbom throw u kojoj se navodi objekat izuzetka koji se izbacuje. Na primer:
throw new PogreanPre cnikIzuzetak("Negativan pre cnik kruga")

ili
throw new PogreanPre cnikIzuzetak("Nedozvoljena vrednost pre cnika: " + pre cnik)

Ako se naredba throw ne nalazi unutar naredbe try kojom se hvata greka usled pogrenog pre cnika kruga, onda metod koji sadri takvu naredbu throw mora obelodaniti da se u metodu moe destiti greka usled pogrenog pre cnika kruga. To se postie pisanjem odgovaraju ce klauzule throws u zaglavlju tog metoda. Na primer:
public void promeniPre cnik(double p) throws PogreanPre cnikIzuzetak { . . . }

Primetimo da klauzula throws u zaglavlju metoda promeniPre cnik() ne bi bila obavezna da je klasa PogreanPre cnikIzuzetak bila denisana tako da bude naslednica klase RuntimeException umesto klase Exception, jer u tom slu caju rukovanje izuzecima tipa PogreanPre cnikIzuzetak ne bi bilo obavezno. Radi ilustracije, u nastavku je naveden primer kompletne klase Krug u kojoj se koristi nova klasa izuzetaka PogreanPre cnikIzuzetak:
public class Krug { private double pre cnik; public Krug(double p) throws PogreanPre cnikIzuzetak { promeniPre cnik(p); } public void promeniPre cnik(double p) throws PogreanPre cnikIzuzetak {

7.4. Denisanje klasa izuzetaka

227

if (p >= 0) pre cnik = p; else throw new PogreanPre cnikIzuzetak("Negativan pre cnik kruga"); } public double getPre cnik() { return pre cnik; } public double povrina() { return Math.PI * pre cnik * pre cnik; } }

U programu u kome se koristi ova klasa Krug, naredbe koje mogu dovesti do greke tipa PogreanPre cnikIzuzetak treba navesti unutar try bloka kojim se rukuje grekama usled pogrenog pre cnika kruga. Na primer:
public class TestKrug { public static void main(String[] args) { try { System.out.println("Konstruisanje prvog kruga ..."); Krug k1 = new Krug(5); k1.promeniPre cnik(-1); System.out.println("Konstruisanje drugog kruga ..."); Krug k2 = new Krug(-5); } catch (PogreanPre cnikIzuzetak e) { System.out.println(e.getMessage()); } } }

U metodu main() se u try bloku konstruie prvi krug i zatim se pokuava promena njegovog pre cnika u negativnu vrednost. To izaziva greku tipa PogreanPre cnikIzuzetak kojom se rukuje u pridruenom catch bloku. Nakon toga se program zavrava i primetimo da se time preska ce konstruisanje drugog kruga (to bi takode cnikIzuzetak). izazvalo greku tipa PogreanPre

G LAVA 8

P ROGRAMSKI UL AZ I IZL AZ

Programi su korisni samo ukoliko se na neki na cin nalaze u interakciji sa spoljanjom sredinom. Pod time se podrazumava dobijanje ( citanje) podataka iz okruenja u program i predavanje (pisanje) podataka iz programa u njegovo okruenje. Ova dvosmerna razmena podataka programa sa okruenjem naziva se ulaz i izlaz programa (ili kra ce U/I ). U knjizi se do sada govorilo samo o jednoj vrsti interaktivnosti interaktivnosti programa s korisnikom koriste ci bilo tekstualni ili gra cki interfejs. Ali c ovek je samo jedan primer izvora i korisnika informacija, jer ra cunar moe biti povezan sa vrlo razli citim ulaznim i izlaznim uredajima. Zbog toga su mogu c a mnoga druga ulazno izlazna okruenja za program u koja spadaju datoteke na disku, udaljeni rac unari, razni ku cni uredaji cno. i sli U Javi se programski ulaz/izlaz jednoobrazno zasniva na apstrakciji toka podataka. Programi c itaju podatke iz ulaznih tokova i upisuju podatke u izlazne tokove. Ulazno/izlazni tokovi podataka su naravno predstavljeni objektima koji obezbeduju metode za c itanje i pisanje podataka sli cne onima koji su ve c kori ceni u tekstualnim programima u knjizi. U stvari, objekti za standardni ulaz i standardni izlaz (System.in i System.out) primeri su tokova podataka. Sve osnovne U/I klase u Javi nalaze se u paketu java.io. Od verzije 1.4 dodate su nove klase u paketima java.nio i java.nio.channels da bi se obezbedile nove mogu cnosti i otklonili neki nedostaci osnovnog pristupa. S novim klasama uveden je koncept kanala kojim se u sutini obezbeduju dodatne mogu cnosti osnovnog na cina rada sa tokovima podataka. Omogu ceno je i baferovanje U/I operacija radi zna cajnog pove canja njihove ekasnosti. U ovom poglavlju se najpre govori o konceptu tokova podataka u Javi kako bi se razumeo opti mehanizam pisanja i c itanja podataka u programima. Zatim se razmatra jedna primena tog koncepta za pisanje i c itanje datoteka na disku.

230

8. P ROGRAMSKI UL AZ I IZL AZ

8.1 Tokovi podataka


Ulaz i izlaz programa u Javi se konceptualno zasniva na jednoobraznom modelu u kome se U/I operacije obavljaju na konzistentan na cin nezavisno od konkretnih ulazno-izlaznih uredaja za koje se primenjuju. U centru ovog logi ckog modela nalazi se pojam toka podataka kojim se apstrahuju vrlo raznorodni detalji rada svih mogu cih U/I uredaja povezanih sa ra cunarom. Na primer, u tekstualnom programu se izlazni tok koristi za ispisivanje podataka u konzolni prozor na ekranu, a ulazni tok se koristi za u citavanje podataka preko tastature. Tok podataka (engl. data stream) je apstraktna reprezentacija prenosa podataka iz nekog izvora podataka u program ili iz programa u neki prijemnik podataka. Tok podataka se vizuelno moe zamisliti kao jednosmerni i sekvencijalni protok bajtova u program ili iz njega. Na slici 8.1 je ilustrovana veza programa, tokova podataka i zi ckih uredaja.
Program
Ulazni tok Izlazni tok

Slika 8.1: Programski ulaz/izlaz u Javi. Na jednom kraju ulaznog ili izlaznog toka uvek se nalazi program. Izlaz programa obavlja se tako to se podaci u programu upisuju u izlazni tok. Izlazni tok na drugom kraju moe biti povezan sa svakim uredajem kojem se bajtovi podataka mogu serijski preneti. U takve prijemnike spadaju datoteke na disku, udaljeni ra cunari povezani mreom, konzolni prozor na ekranu ili zvu cna kartica povezana sa zvu cnicima.1
1 tampa c je izlazni uredaj c se u Javi smatra koji se ne moe koristiti za izlazni tok. tampa

gra ckim uredajem i tampanje je konceptualno vrlo sli cno prikazivanju gra ckih elemenata na ekranu.

8.1. Tokovi podataka

231

Ulaz programa obavlja se tako to se podaci u programu c itaju iz ulaznog toka. Na drugom kraju ulaznog toka moe se nalaziti, u principu, svaki izvor serijskih bajtova podataka. To su obi cno datoteke na disku, udaljeni ra cunari ili tastatura.

Binarni i tekstualni tokovi


Kada se govori o ulazu i izlazu programa pre svega treba imati na umu dve iroke kategorije podataka koje odraavaju na cin na koji se oni predstavljaju: binarni podaci i tekstualni podaci. Binarni podaci se predstavljaju u binarnom obliku na isti na cin na koji se zapisuju unutar ra cunara. Binarni podaci su dakle obi cni nizovi nula i jedinica c ija interpretacija nije odmah prepoznatljiva. Tekstualni podaci se predstavljaju nizovima znakova c ije se zna cenje obi cno moe lako prepoznati. Ali pojedina cni znakovi u tekstualnom podatku se i sami zapisuju u binarnom obliku prema raznim emama kodiranja. Prema tome, svi podaci u ra cunarima su naravno binarni, ali su tekstualni podaci u ovom kontekstu samo vii stepen apstrakcije u odnosu na bezli can niz nula i jedinica. Da bi se ove dve kategorije ulazno-izlaznih podataka uzele u obzir, u Javi se posledi cno razlikuju dve vrste tokova: binarni U/I tokovi i tekstualni U/I tokovi. Za ove dve vrste tokova se primenjuju razli citi postupci prilikom pisanja i c itanja podataka. Najvanija razlika izmedu binarnih i tekstualnih tokova je to to tekstualni tok obezbeduje automatsko konvertovanje bajtova prilikom njihovog prenosa iz programa u spoljanje okruenje ili u program iz spoljanjeg okruenja. Ukoliko se koristi binarni tok, bajtovi se neizmenjeni prenose iz programa ili u njega. Da bismo bolje razumeli razliku izmedu binarnih i tekstualnih tokovima u Javi, razmotrimo jedan konkretan primer izlaza programa: pretpostavimo da se u programu ceo broj 167 tipa byte upisuje u datoteku na disku. Broj 167 je u programu zapisan unutar jednog bajta u binarnom obliku 10100111 (ili ekvivalentno 0xA7 u heksadecimalnom zapisu). Ukoliko se za pisanje broja 167 koristi binarni tok, jedan bajt odgovaraju ce binarne vrednosti 10100111 (0xA7 heksadecimalno) bi ce upisan neizmenjen u datoteku. Ali ako se za njegovo pisanje koristi tekstualni tok, onda se broj 167 upisuje kao niz znakova 1, 6 i 7. Pri tome se svaki znak u nizu konvertuje speci cnom kodnom emom u odgovaraju ci bajt koji se zapravo upisuje u datoteku na disku. Ako se, recimo, koristi ASCII ema, onda znaku 1 odgovara jedan bajt c ija ASCII vrednost iznosi 0x31 u heksadecimalnom zapisu (ili 00110001 u binarnom obliku), znaku 6 odgovara bajt 0x36 i znaku 7 odgovara bajt 0x37. Prema tome, za broj 167 tipa byte u

232

8. P ROGRAMSKI UL AZ I IZL AZ

datoteku se redom upisuju tri bajta 0x31, 0x36 i 0x37. Dva na cina ispisivanja numeri cke vrednosti u ovom primeru ilustrovana su na slici 8.2. (Sadraj pojedinih bajtova na toj slici prikazan je u kra cem, heksadecimalnom zapisu.)
Disk Program
rni bina tok
datoteka

A7

byte b = 167; b A7

tekst ualn i tok

datoteka

31 36 37
ASCII 1 6 7

Slika 8.2: Upisivanje broja 167 u datoteku. S druge strane, pretpostavimo da se u programu string "ab1" upisuje u datoteku na disku. Ovaj string je u programu zapisan kao niz znakova a, b i 1, a svaki znak je predstavljen pomo cu dva bajta prema Unicode emi. Kako znaku a u ovoj emi odgovaraju dva bajta 0x0041, znaku b odgovaraju dva bajta 0x0042 i znaku 1 odgovaraju dva bajta 0x0031, to je u programu string "ab1" predstavljen sa est bajtova. Ukoliko se za pisanje stringa "ab1" koristi binarni tok, ovih est bajtova bi ce upisani neizmenjeni u datoteku. Ali ako se za pisanje koristi tekstualni tok, onda se svaki Unicode znak redom konvertuje na osnovu speci cne kodne eme i odgovaraju ci bajtovi se upisuje u datoteku na disku. Dakle, ukoliko se koristi podrazumevana ASCII ema za izlaznu datoteku, za string "ab1" se u datoteku redom upisuju tri bajta 0x41, 0x42 i 0x31, jer u ASCII emi znakovima a, b i 1 odgovaraju bajtovi, respektivno, 0x41, 0x42 i 0x31. Dva na cina ispisivanja stringa u ovom primeru ilustrovana su na slici 8.3, na kojoj je sadraj pojedinih bajtova opet prikazan u heksadecimalnom zapisu. Za ulaz programa primenjuje se sli can princip za binarne i tekstualne tokove prilikom c itanja podataka, samo u obrnutom smeru. Tako, ako se neka datoteka sa ASCII znakovima c ita sa diska kori cenjem tekstualnog toka, onda se ASCII bajtovi najpre konvertuju u Unicode znakove, a zatim se ovi Unicode znakovi pretvaraju u odgovaraju cu vrednost odredenog tipa navedenog u pro gramu. Binarni tokovi su mnogo ekasniji za ulaz ili izlaz programa, jer se kod njih ne gubi dodatno vreme potrebno za konverziju bajtova prilikom prenosa. To

8.1. Tokovi podataka

233

Disk Program
k ni to String s = "ab1"; binar
s 00 41 00 42 00 31 a b 1
datoteka

00 41 00 42 00 31

Unicode

teks tual ni to k

datoteka

41 42 31
ASCII ab1

Slika 8.3: Upisivanje stringa "ab1" u datoteku.

zna ci i da su podaci upisani kori cenjem binarnog toka portabilni, jer se mogu c itati neizmenjeni na ra cunaru koji je razli cit od onog na kome su upisani. Ipak, binarni tokovi se mnogo rede cno samo kada se radi o koriste, prakti velikim koli cinama prenosa podataka u specijalnim primenana (na primer, kod baza podataka ili kod direktnih ra cunarra cunar komunikacija). Osnovni nedostatak binarnih tokova je to to su dobijeni binarni podaci ne citljivi za ljude, pa je potreban dodatni napor da bi se oni mogli interpretirati.

Hijerarhija klasa za ulaz i izlaz


Klase u Javi kojima se obezbeduje ulaz i izlaz programa nalaze se uglav nom u paketu java.io. Pored nekih pomo cnih klasa, ve cina klasa u ovom paketu sa cinjava hijerarhiju na c ijem vrhu se nalaze c etiri apstraktne klase za svaku vrstu tokova podataka: InputStream za ulazne binarne tokove OutputStream za izlazne binarne tokove Reader za ulazne tekstualne tokove Writer za izlazne tekstualne tokove Klase koje nasleduju ove apstraktne klase uglavnom proiruju osnovne mogu cnosti rada s tokovima podataka zavisno od zi ckih uredaja preko kojih se obavlja programski ulaz ili izlaz. U verziji Java 1.4 dodate su nove klase u paketima java.nio i java.nio.channels da bi se obezbedile nove i pove cala ekasnot postoje cih U/I operacija. Sve klase binarnih tokova nasleduju jednu od apstraktnih klasa Input cke vrednosti upisuju putem toka Stream ili OutputStream. Ako se numeri tipa OutputStream, rezultati c e biti u binarnom obliku u kojem su te vrednosti

234

8. P ROGRAMSKI UL AZ I IZL AZ

zapisane u memoriji ra cunara. Zbog toga su upisane vrednosti ne citljive za ljude, ali se mogu ispravno u citavati u program putem toka tipa InputStream. Pisanje i c itanje podataka na ovaj na cin je vrlo ekasno, jer ne dolazi do nikakvog konvertovanja bajtova prilikom prenosa. Sve klase tekstualnih tokova nasleduju jednu od apstraktnih klasa Reader ili Writer. Ako se numeri cke vrednosti upisuju putem toka tipa Writer, rezultati se konvertuju u nizove znakova koji predstavljaju te vrednosti u c itljivom obliku. U citavanje brojeva putem toka tipa Reader obuhvata obrnuto prevodenje niza znakova u binarni zapis prema tipu odgovaraju ce promenljive. Obratite panju na to da kod tekstualnih tokova obi cno dolazi do konvertovanja znakova i prilikom c itanja i pisanja stringova. U tom slu caju se znakovi zapisani na spoljanjem medijumu prema jednoj kodnoj emi (ASCII ili neka ema za neenglesko pismo) konvertuju u Unicode znakove, ili obrnuto. Osnovne U/I klase InputStream, OutputStream, Reader i Writer obezbeduju samo vrlo primitivne U/I operacije. Na primer, klasa InputStream sadri objektni metod
public int read() throws IOException

kojim se jedan bajt iz ulaznog binarnog toka c ita u obliku celog broja iz opsega od 0 do 255. Ukoliko se naide itanja, metod na kraj ulaznog toka prilikom c read() kao rezultat vra ca vrednost 1. Ako se c itanje ne moe obaviti usled neke greke, izbacuje se izuzetak tipa IOException. Kako je izuzetak ovog tipa proveravan izuzetak, njime se mora rukovati u programu tako to se metod read() navodi unutar naredbe try ili se u zaglavlju drugog metod koji poziva metod read() navodi klauzula throws IOException. Za pisanje jednog bajta u izlazni binarni tok, u klasi OutputStream nalazi se metod:
public void write(int b) throws IOException

Primetimo da je parametar ovog metoda tipa int a ne byte, ali se pre same operacije upisivanja automatski izvrava konverzija tipa ovog parametra u byte. To prakti cno zna ci da se efektivno zapravo upisuje bajt najmanje teine argumenta metoda write(). Klase Reader i Writer sadre analogne metode read() i write() za c itanje i pisanje tekstualnih tokova, s tom razlikom to ovi metodi c itaju i piu pojedina cne znakove, a ne bajtove. Vra cena vrednost metoda read() je 1 ukoliko se naide itanja, dok se u normalnom na kraj ulaznog toka prilikom c slu caju mora izvriti konverzija tipa te vra cene vrednosti u char da bi se dobio pro citani znak. Naravno, upotreba ovih primitivnih U/I operacija u programima bila bi za programere prili cno mukotrpna. Zbog toga se u praksi skoro uvek koriste

8.1. Tokovi podataka

235

U/I operacija vieg nivoa koje su realizovane metodima u klasama koje su naslednice c etiri osnovne klase na vrhu hijerarhije. O tome se vie govori u nastavku poglavlja.

Citanje i pisanje binarnih podataka


Klase InputStream i OutputStream omogu cavaju c itanje i pisanje samo pojedina cnih bajtova, ali ne i podataka koji su zapisani u sloenijem formatu. Da bi se u programima omogu cilo c itanje i pisanje vrednosti primitivnih tipova u internom binarnom obliku, u paketu java.io se nalaze klase DataInputStream i DataOutputStream koje su naslednice osnovnih klasa InputStream i OutputStream. Klasa DataOutputStream sadri metod writeDouble(double x) za pisanje realnih brojeva tipa double, metod writeInt(int x) za pisanje celih brojeva tipa int i tako dalje za svaki primitivni tip u Javi. Pored toga, objekat tipa OutputStream moe se umotati u objekat tipa DataOutputStream da bi se na objekat nieg nivoa apstrakcije mogli primeniti ovi metodi vieg nivoa. To se postie prostim navodenjem objekta tipa OutputStream da bude argument konstruktora klase DataOutputStream. Na primer, ako je promenljiva b tipa OutputStream, onda se moe pisati
DataOutputStream a = new DataOutputStream(b);

da bi se objekat b umotao u objekat a i tako omogu cilo pisanje vrednosti primitivnih tipova u internom formatu na uredaj predstavljen objektom b tipa OutputStream. Za c itanje vrednosti primitivnih tipova zapisanih u internom binarnom obliku slui analogna klasa DataInputStream. Tako ova klasa sadri metode readDouble(), readInt() i tako dalje za c itanje vrednosti svih primitivnih tipova. Sli cno, objekat tipa InputStream moe se umotati u objekat tipa DataInputStream da bi se na objekat nieg nivoa apstrakcije mogli primeniti ovi metodi vieg nivoa. To se isto postie prostim navodenjem objekta tipa InputStream da bude argument konstruktora klase DataInputStream. U nekim primenama treba c itati znakove iz binarnog toka tipa Input itanje Stream ili pisati znakove u binarni tok tipa OutputStream. Kako za c i pisanje znakova slue klase Reader i Writer, u takvim slu cajevima moe se koristiti standardna mogu cnost umotavanja jednog toka u drugi. Klase InputStreamReader i OutputStreamWriter namenjene su upravo za ovu mogu cnost. Ako su bIn promenljiva tipa InputStream i bOut promenljiva tipa OutputStream, onda se naredbama
Reader zIn = new InputStreamReader(bIn);

236

8. P ROGRAMSKI UL AZ I IZL AZ

Writer zOut = new OutputStreamWriter(bOut);

konstruiu tekstualni tokovi koji se mogu koristiti za c itanje znakova iz binarnog toka bIn i pisanje znakova u binarni tok bOut. Jedan primer ove tehnike moe se na ci u programima za c itanje znakova iz standardnog ulaza System.in, koji u Javi iz istorijskih razloga predstavlja binarni tok tipa InputStream. Da bi se zato olakalo c itanje znakova, objekat standardnog ulaza moe se umotati u tekstualni tok tipa Reader:
Reader zIn = new InputStreamReader(System.in);

Drugi primer ove tehnike sre ce se u mrenom programiranju. Ulazni i izlazni tokovi podataka koji su povezani s mrenim konekcijama predstavljeni su binarnim a ne tekstualnim tokovima. Ali, radi lakeg slanja i primanja tekstualnih podataka preko mree, binarni tokovi se mogu umotati u tekstualne tokove. O mrenom programiranju se vie govori u poglavlju 10.

Citanje i pisanje tekstualnih podataka


Klasama InputStream i OutputStream za binarne tokove odgovaraju klase
Reader i Writer za tekstualne tokove, jer klase Reader i Writer omogu ca-

vaju c itanje i pisanje samo pojedina cnih znakova iz skupa Unicode dvobajtnih znakova. Sli cno kao za binarne tokove, u radu sa tekstualnim tokovima se dodatne mogu cnosti za tokove nieg nivoa apstrakcije mogu obezbediti umotavanjem tih tokova u druge tokova vieg nivoa. Dobijeni rezultat je takode itanje ili pisanje poda jedan tok podataka i zato se moe koristiti za c taka, ali uz pomo c U/I operacija vieg nivoa. Klasa PrintWriter je naslednica klase Writer i sadri metode za ispisivanje vrednosti svih osnovnih tipova podataka u uobi cajenom obliku c itljivom za ljude. U stvari, ove metode smo ve c upoznali u radu sa standardnim izlazom System.out. Naime, ako je out promenljiva tipa PrintWriter, onda se za pisanje u tok na koji ukazuje ova promenljiva mogu koristiti slede ci metodi: out.print(x) vrednost izraza x upisuje se u izlazni tok u obliku niza znakova. Izraz x moe biti bilo kog tipa, primitivnog ili klasnog. Objekat se reprezentuje nizom znakova koriste ci njegov metod toString(), a vrednost null se reprezentuje nizom znakova "null". out.println() znak za kraj reda upisuje se u izlazni tok. out.println(x) vrednost izraza x i zatim znak za kraj reda upisuju se u izlazni tok. Ovaj efekat je ekvivalentan naredbama out.print(x) i out.println() napisane jedna iza druge.
PrintWriter.

8.1. Tokovi podataka

237

out.printf(s,x1,x2, ...) vrednosti izraza x1, x2 i tako dalje upisuju se na osnovu specikacije formata s u izlazni tok. Prvi parametar s je string kojim se precizno opisuje kako se vrednosti argumenata x1, x2 i tako dalje reprezentuju nizovima znakova radi upisivanja u izlazni tok. Mnogobrojna specikacija formata ove reprezentacije je potpuno ista kao u slu caju standardnog izlaza System.out, a zainteresovani c itaoci mogu dodatne informacije o tome potraiti u dokumentaciji Jave. Obratite panju na to da nijedan od ovih metoda ne izbacuje nijedan izuzetak tipa IOException. Umesto toga, ukoliko se desi neka greka prilikom izvravanja nekog od ovih metoda, u klasi PrintWriter se interno hvataju svi U/I izuzeci i zatim se oni obraduju tako to se menja vrednost jednog privatnog indikatora greke. Zbog toga klasa PrintWriter sadri i metod
public boolean checkError()

kojim se u programu moe proveriti vrednost indikatora greke kod pisanja podataka. Na ovaj na cin je omogu ceno da se u programu ne mora na uobi cajen na cin rukovati izuzecima usled izlaznih greaka, ali u robustnim programima greke treba izbegavati kori cenjem metoda checkError(). Da bi objekat tipa PrintWriter posluio kao omota c za objekat osnovne klase Writer, postupa se na standardni na cin: konstruie se novi objekat tipa PrintWriter koriste ci objekat tipa Writer kao argument konstruktora. Na primer, ako je zOut tipa Writer, onda se moe pisati:
PrintWriter out = new PrintWriter(zOut);

Kada se podaci upisuju u izlazni tok out kori cenjem nekog od metoda print() klase PrintWriter, onda se ti podaci zapravo upisuju u izlazni tok koji je predstavljen objektom zOut. To moe biti, recimo, datoteka na disku ili udaljeni ra cunar u mrei. Moda je interesantno znati da na po cetku u Javi nije postojala standardna klasa, simetri cna klasi PrintWriter, koja omogu cava lako c itanje vrednosti osnovnih tipova podataka zapisanih u uobi cajenom obliku c itljivom za ljude. Tek od verzije Java 1.5 u paketu java.util dodata je klasa Scanner koja ovaj problem reava na relativno zadovoljavaju ci na cin. Problem lakog c itanja ulaznih tekstualnih tokova mu cio je naro cito programere po cetnike, jer se za c itanje podataka iz standardnog ulaza moralo primeniti nekoliko umotavanja objekata jedan u drugi da bi se dolo do iole prihvatljivog nivoa za ulazne operacije. U paketu java.io nalazi se, na primer, standardna klasa BufferedReader koja sadri metod za c itanje jednog celog reda znakova iz ulaznog toka:
Scanner.

238

8. P ROGRAMSKI UL AZ I IZL AZ

public String readLine() throws IOException

Tako, da bi se iz standardnog ulaza c itao ceo jedan red tekstualnih podataka, objekat System.in tipa InputStream mora se najpre umotati u tok tipa InputStreamReader, a ovaj u tok tipa BufferedReader:
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

Nakon konstruisanja objekta in na ovaj na cin, u programu se naredbom


in.readLine() moe u citati ceo red teksta iz standardnog ulaza. Ovim po-

stupkom se moe c itati red-po-red iz standardnog ulaza, ali se ne obezbeduje izdvajanje pojedina cnih podataka iz celih redova ulaznog teksta. Za tako neto, ukoliko je potrebno u programu, moraju se pisati posebni metodi. Ovi problemi, oteani obaveznim rukovanjem izuzetka koji moe izbaciti metod readLine(), doprineli su looj reputaciji ulaznih mogu cnosti jezika Java. c za ulazni tok podataka. Izvor znaObjekat tipa Scanner slui kao omota kova navodi se u konstruktoru skenera i moe biti tipa InputStream, Reader, String ili File. (Ako se koristi string za ulazni tok, znakovi stringa se c itaju redom od po cetka do kraja. O klasi File govori se u narednom odeljku.) Na primer, za c itanje podataka preko tastature koja je povezana sa standardnim ulazom koristili smo klasu Scanner:
Scanner tastatura = new Scanner(System.in);

Ako je zIn tok tipa Reader, onda se za c itanje tog tekstualnog toka moe konstruisati skener na slede ci na cin:
Scanner skener = new Scanner(zIn);

Prilikom c itanja toka podataka, skener deli ulazni niz znakova u tokene, odnosno grupe znakova koji c ine jednu celinu. Tokeni su recimo grupa znakova koji predstavljaju jedan broj tipa double ili int ili sli cnu literalnu vrednost. Podrazumeva se da su tokeni u ulaznom toku razdvojeni znakovima beline (razmaci, tabulatori ili novi redovi) koji se obi cno preska cu tokom c itanja. Klasa Scanner sadri objektne metode za c itanje tokena razli citih tipova: ita slede ci token koji se kao rezultat vra ca u obliku objekta next() c tipa String. nextInt(), nextDouble() i tako dalje c ita slede ci token koji se konvertuje u vrednost tipa int, double i tako dalje. Postoje odgovaraju ci metodi za c itanje vrednosti svih primitivnih tipova podataka osim Char. nextLine() c ita sve znakove do slede ceg znaka za novi red. Pro citani znakovi se kao rezultat vra caju u obliku objekta tipa String. Obratite panju na to da se ovaj metod ne zasniva na c itanje tokena, nego su

8.1. Tokovi podataka

239

eventualni znakovi razmaka i tabulatora deo vra cenog stringa. Pored toga, grani cni znak za novi red se c ita, ali nije deo vra cenog stringa. Ovi metodi u klasi Scanner mogu dovesti do izbacivanja izuzetaka. Tako, ukoliko se pokuava c itanje ulaznog toka iza njegovog kraja, izbacuje se izuzetak tipa NoSuchElementException. Ili, metod nextInt() izbacuje izuzetak tipa InputMismatchException ukoliko slede ci token ne predstavlja celobrojnu vrednost. Svi ovi izuzeci pripadaju kategoriji neproveravanih izuzetaka i zato ne zahtevaju obavezno rukovanje u programima. U klasi Scanner se nalaze i metodi kojima se moe proveravati da li se c itanjem dolo do kraja ulaznog toka, kao i to da li je slede ci token speci cnog tipa: ca logi cku vrednost ta cno ili neta cno zavisno od toga hasNext() vra da li se u ulaznom toku nalazi bar jo jedan token. hasNextInt(), hasNextDouble() i tako dalje vra ca logi cku vrednost ta cno ili neta cno zavisno od toga da li slede ci token u ulaznom toku predstavlja vrednost speci cnog tipa. hasNextLine() vra ca logi cku vrednost ta cno ili neta cno zavisno od toga da li se u ulaznom toku nalazi bar jo jedan red znakova.

Citanje i pisanje objekata


Scanner/PrintWriter i DataInputStream/DataOutputStream su klase u Javi koje obezbeduju jednostavan ulaz/izlaz za vrednosti primitivnih tipova podataka. Ali budu ci da su programi u Javi objektno orijentisani, postavlja se pitanje kako se obavlja ulaz/izlaz za objekte? Bez ugradenog mehanizma u programskom jeziku, programski izlaz obje kata mora se uraditi na indirektan na cin. To se svodi najpre na pronalaenje neke posebne forme predstavljanja objekata u programu preko vrednosti primitivnih tipova i zatim pisanja ovih vrednosti u binarni ili tekstualni tok. U obrnutom smeru, za programski ulaz objekata onda treba c itati odgovaraju ce vrednosti primitivnih tipova i rekonstruisati originalne objekte. Ovaj postupak kodiranja i dekodiranja objekata primitivnim vrednostima radi pisanja i c itanja objekata u programu naziva se serijalizacija i deserijalizacija objekata. Serijalizacija i deserijalizacija za kompleksne objekte nije nimalo jednostavan zadatak. Zamislimo samo ta bi sve u jednom gra ckom programu moralo da se uradi za serijalizaciju najprostijeg objekta tipa JButton. Naime, morale bi da se sa cuvaju trenutne vrednosti svih atributa tog objekta: boja, font, oznaka, pozicija i tako dalje. Ako je neki atribut klasnog tipa (kao to je pozadina tipa Color), moraju se sa cuvati i vrednosti atributa tog objekta.

240

8. P ROGRAMSKI UL AZ I IZL AZ

To naalost nije kraj, jer ako je klasa naslednica druge klase (kao to je klasa JButton naslednica klase AbstractButton), onda se moraju c uvati i vrednosti atributa koji pripadaju nasledenom delu objekta. U Javi, sre com, postoji ugradeni mehanizam za c itanje i pisanje objekata koji sledi opte principe tokova podataka. Za ulaz/izlaz objekata sa automatskom serijalizacijom i deserijalizacijom koriste se klase ObjectInputStream i ObjectOutputStream. Klasa ObjectInputStream je naslednica klase InputStream i klasa ObjectOutputStream je naslednica klase OutputStream. Tokovi tipa ObjectInputStream i ObjectOutputStream za c itanje i pisanje objekata konstruiu se kao omota ci binarnih tokova tipa InputStream i OutputStream kori cenjem slede cih konstruktora:
public ObjectInputStream(InputStream bIn) public ObjectOutputStream(OutputStream bOut)

U klasi ObjectInputStream za c itanje jednog objekta slui metod:


public Object readObject()

Sli cno, u klasi ObjectOutputStream za pisanje jednog objekta slui metod:


public void writeObject(Object o)

Svaki od ova dva metoda moe izbaciti neke proveravane izuzetke i zato se pozivi ovih metoda moraju nalaziti unutar dela programa u kojima se tim izuzecima rukuje na odgovaraju ci na cin. Primetimo i da je vra cena vrednost metoda readObject() tipa Object, to zna ci da se ona obi cno mora eksplicitno konvertovati u drugi, korisniji klasni tip u programu. U klasi ObjectOutputStream nalaze se i metodi za pisanje vrednosti primitivnih tipova u binarni tok kao to su writeInt(), writeDouble() i tako dalje. Sli cno, u klasi ObjectInputStream nalaze se i odgovaraju ci metodi za c itanje vrednosti primitivnih tipova. U stvari, klase ObjectInputStream i ObjectOutputStream potpuno zamenjuje funkcionalnost klasa DataInputStream i DataOutputStream, tako da se ovaj drugi par klasa moe zanemariti i koristiti samo prvi par. Klase ObjectInputStream i ObjectOutputStream mogu se koristiti za c itanja i pisanje samo onih objekata koji implementiraju specijalni interfejs Serializable. To ipak nije veliko ograni cenje, jer interfejs Serializable zapravo ne sadri nijedan metod. Ovaj interfejs slui samo kao marker kojim se kompajleru ukazuje da su objekti implementiraju ce klase predvideni za ulaz/izlaz i da za njih zato treba dodati ugradeni mehanizam za automatsku serijalizaciju i deserijalizaciju. U deniciji klase c iji su objekti predvideni za ulaz/izlaz ne treba dakle implementirati nikakve dodatne metode, nego samo u zaglavlju denicije klase treba dodati implements Serializable. Mnoge

8.2. Datoteke

241

standardne klase u Javi su denisane sa ovom klauzulom kako bi se njihovi objekti mogli lako c itati i pisati u programima. Primenom klasa ObjectInputStream i ObjectOutputStream za c itanje i pisanje objekata koriste se binarni tokovi. Drugim re cima, objekti se predstavljaju u binarnom obliku ne citljivom za ljude tokom c itanja i pisanja. To je dobro zbog ekasnosti, ali ima i svojih nedostataka. Prvo, binarni format objekata je speci can za trenutnu verziju Jave koji se moe promeniti u nekoj budu coj verziji. Drugo, taj format nije obi cno isti u drugim programskim jezicima, pa objekti upisani na spoljanji medijum u Java programu ne mogu se lako c itati u programima koji su napisani na nekom drugom jeziku. Zbog ovih razloga, binarni tokovi za c itanje i pisanje objekata korisni su samo za privremeno c uvanje objekata na spoljanjim medijumima ili za prenos objekata iz jednog Java programa preko mree drugom Java programu. Tekstualni tokovi su korisniji za trajnije c uvanje objekata ili za prenos objekata programima koji nisu napisani u Javi. U tom slu caju koriste se standardizovani na cini za predstavljanje objekata u tekstualnom obliku (na primer, XML format).

8.2 Datoteke
Vrednosti koje su u toku rada programa nalaze u promenljivim, nizovima i objektima jesu privremenog karaktera i gube se po zavretku programa. Da bi se te vrednosti trajno sa cuvale, one se mogu upisati na neki spoljanji uredaj permanentne memorije koja se deli u celine koje se zovu datoteke (engl. le). Datoteka predstavlja posebnu grupu podataka na disku, USB e-disku, DVDju ili na nekom drugom tipu spoljanjeg uredaja permanentne memorije. Po red toga to slue za trajno c uvanje podataka, datoteke se mogu prenosti na druge ra cunare i kasnije c itati drugim programima. Sistem datoteka na spoljanjem uredaju podeljen je u direktorijume (ili foldere) koji sadre datoteke, ali i druge direktorijume. Datoteke i direktorijumi se raspoznaju po jedinstvenim imenima kojima se nazivaju prilikom formiranja. Programi mogu c itati podatke iz postoje cih datoteka i upisivati podatke u postoje ce ili nove datoteke. Programski ulaz i izlaz u Javi standardno se obavlja putem tokova podataka. Vrednosti iz programa se zapisuju u tekstualnom obliku u datoteci pomo cu tekstualnog toka tipa FileWriter, a takve vrednosti zapisane u tekstualnom obliku u datoteci c itaju se pomo cu tekstualnog toka tipa FileReader. Klasa FileWriter je naslednica klase Writer i klasa FileReader je naslednica klase Reader. Podsetimo se da se apstraktne

242

8. P ROGRAMSKI UL AZ I IZL AZ

klase Reader i Writer nalaze na vrhu hijerarhije klasa kojima se predstavljaju tekstualni tokovi podataka. Citanje i pisanje podataka u binarnom formatu za datoteke u Javi se obavlja uz pomo c klasa FileInputStream i FileOutputStream koje su naslednice apstraktnih klasa InputStream i OutputStream. U ovom odeljku, medutim, govori se samo o tekstualnom ulazu/izlazu za datoteke, jer je primena klasa FileInputStream i FileOutputStream potpuno analogna radu sa klasama FileReader i FileWriter. Sve ove klase se nalaze u paketu java.io.

Citanje i pisanje datoteka


U klasi FileReader nalazi se konstruktor kojim se konstruie ulazni tekstualni tok za c itanje podataka iz datoteke. Ime datoteke koja se eli c itati navodi se kao argument konstruktora. Ukoliko datoteka s tim imenom ne postoji, konstruktor izbacije izuzetak tipa FileNotFoundException c ije je rukovanje u programu obavezno. Pretpostavimo da se na disku nalazi datoteka ulaz.txt sa podacima koje treba c itati u programu. U slede cem programskom fragmentu se konstruie ulazni tok za tu datoteku:
FileReader ulaz; // ulazni tok

// Konstruisanje ulaznog toka za datoteku ulaz.txt try { ulaz = new FileReader("ulaz.txt"); } catch (FileNotFoundException e) { // Rukovanje izuzetkom . . . }

Obratite panju u ovom fragmentu na to da je promenljiva ulaz denisana izvan naredbe try. To je neophodno, jer bi ina ce ta promenljiva bila lokalna za try blok i ne bi se mogla koristiti izvan njega. Pored toga, kako je klasa FileNotFoundException naslednica klase IOException, u prethodnoj klazuli catch se moe navesti ova roditeljka klasa. Optije, skoro svaka speci cna U/I greka moe se hvatati u klazuli catch pisanjem najoptijeg tipa greke IOException. Nakon uspenog konstruisanja ulaznog toka tipa FileReader za datoteku ulaz.txt,2 podaci iz ove datoteke mogu se redom c itati. Ali poto klasa Fileod klase Reader za c itanje Reader sadri samo primitivne metode nasledene
2 Konstruisanje ulaznog toka za datoteku naziva se kolokvijalno otvaranje datoteke.

8.2. Datoteke

243

podataka, radi komfornijeg rada obi cno se koristi objekat tipa Scanner da bude omota c objekta tipa FileReader:
Scanner ulaz; // ulazni tok

// Konstruisanje omota ca ulaznog toka za datoteku ulaz.txt try { ulaz = new Scanner(new FileReader("ulaz.txt")); } catch (FileNotFoundException e) { // Rukovanje izuzetkom . . . }

Na ovaj na cin se datoteka ulaz.txt na disku moe c itati kori cenjem naredbi ulaz.nextInt(), ulaz.nextLine() i sli cno, odnosno potpuno isto kako se koristi skener za c itanje vrednosti iz standardnog ulaza preko tastature. Pisanje podataka u datoteku je vrlo sli cno c itanju podataka i ne zahteva neki dodatni napor. Izlazni tok za pisanje u datoteku konstruie se da bude objekat tipa FileWriter. Poto konstruktor klase FileWriter moe izbaciti proveravan izuzetak tipa IOException, konstruisanje odgovaraju ceg objekta izvodi se obi cno unutar naredbe try. Isto tako, radi lakeg pisanja podataka analogno se koristi i objekat tipa PrintWriter da bude omota c objekta tipa FileWriter. Na primer, ukoliko podatke iz programa treba upisati u datoteku izlaz.txt, to se moe uraditi otprilike na slede ci na cin:
PrintWriter izlaz; // izlazni tok

// Konstruisanje omota ca izlaznog toka za datoteku izlaz.txt try { izlaz = new PrintWriter(new FileWriter("izlaz.txt")); } catch (IOException e) { // Rukovanje izuzetkom . . . }

Ukoliko na disku ne postoji datoteka pod imenom izlaz.txt, obrazuje se nova datoteka i u nju se upisuju podaci. Ukoliko datoteka sa ovim imenom ve c postoji, njen trenutni sadraj bi ce obrisan bez upozorenja i bi ce zamenjen podacima koji se u programi ispisuju. Ovaj c esto neeljeni efekat moe se izbe ci prethodnom proverom da li izlazna datoteka ve c postoji na disku. (O ovome se detaljnije govori u narednom odeljku.) Greka tipa IOException moe se dogoditi prilikom konstruisanja objekta izlaznog toga ukoliko, recimo, program nema prava da obrazuje datoteke u aktuelnom direktorijumu ili je ceo disk zati cen od pisanja (engl. write-protected).

244

8. P ROGRAMSKI UL AZ I IZL AZ

Nakon zavretka rada s datotekom u programu, datoteku treba zatvoriti pozivom metoda close() za odgovaraju ci tok. Zatvorena datoteka se vie ne moe koristiti za c itanje ili pisanje, osim ako se ponovo ne otvori za novi tok podataka. Ukoliko se datoteka ne zatvori eksplicitno metodom close(), ona se automatski zatvara od strane JVM kada se program zavri. Ovo ipak ne zna ci da operaciju zatvaranja datoteke ne treba uzeti ozbiljno, iz dva razloga. Prvo, memorijski resursi rezervisani u programu za rad sa datotekom bi ce zauzeti sve vreme rada programa, iako se moda mogu osloboditi mnogo pre kraja rada programa. Drugi, ozbiljniji nedostatak je to to u slu caju izlaznih datoteka neki podaci mogu biti izgubljeni. To se deava zato to podaci koji se u programu piu u datoteku obi cne se radi ekasnosti ne upisuju odmah u nju, nego se privremeno upisuju u oblast glavne memorije koja se naziva bafer. Tek kad se popuni ceo bafer, podaci se iz njega zi cki ispisuju u datoteku. Zato ukoliko se datoteka nasilno zatvori na kraju rada programa, neki podaci u delimi cno popunjenom baferom mogu ostati zi cki neupisani u datoteci. Jedan od zadataka metoda close() za izlazni tok je upravo to da se svi podaci iz bafera upiu u datoteku. Primetimo i da ako je u programu potrebno isprazniti bafer bez zatvaranja datoteke, onda se za svaki izlazni tok moe koristiti metod flush().

Primer: sortiranje datoteke brojeva


U ovom kompletnom primeru se ilustruju osnovne tehnike rada sa datotekama u Javi o kojima smo govorili u prethodnim odeljcima. Pretpostavlja se da se na disku nalazi datoteka podaci.txt sa brojevima u proizvoljnom redosledu. Zadatak programa prikazanog u nastavku je da brojeve iz te datoteke najpre u cita u jedan niz, zatim sortira taj niz i, na kraju, upie vrednosti rezultuju ceg sortiranog niza u drugu datoteku sort-podaci.txt.
import java.io.*; import java.util.*; public class SortiranjeDatoteke { public static void main(String[] args) { Scanner ulaz; // ulazni tekstualni tok za citanje podataka PrintWriter izlaz; // izlazni tekstualni tok za pisanje podataka double[] brojevi = new double[1000]; // niz u citanih brojeva int k = 0; // broj u citanih podataka // Konstruisanje ulaznog toka za datoteku podaci.txt try { ulaz = new Scanner(new FileReader("podaci.txt"));

8.2. Datoteke

245

} catch (FileNotFoundException e) { System.out.print("Ulazna datoteka podaci.txt "); System.out.println("nije na dena!"); return; // kraj rada programa zbog greke } // Konstruisanje izlaznog toka za datoteku sort-podaci.txt try { izlaz = new PrintWriter(new FileWriter("sort-podaci.txt")); } catch (IOException e) { System.out.print("Otvaranje izlazne datoteke "); System.out.println("sort-podaci.txt nije uspelo!"); System.out.println("Greka: " + e); ulaz.close(); // zatvoriti ulaznu datoteku return; // kraj rada programa zbog greke } try { // U citavanje brojeva iz ulazne datoteke u niz while (ulaz.hasNext()) { // citanje sve do kraja ulazne datoteke brojevi[k] = ulaz.nextDouble(); k = k + 1; } // Sortiranje u citanih brojeva u nizu u opsegu od 0 do k-1 Arrays.sort(brojevi, 0, k); // Upisivanje sortiranih brojeva u izlaznu datoteku for (int i = 0; i < k; i++) izlaz.println(brojevi[i]); System.out.println("Ulazna datoteka je sortirana."); } catch (RuntimeException e) { System.out.println("Problem u radu sa nizom!"); System.out.println("Greka: " + e.getMessage()); } catch (Exception e) { System.out.println("Problem pri citanju/pisanju podataka!"); System.out.println("Greka: " + e.getMessage()); } finally { // Zatvaranje obe datoteke, u svakom slu caju ulaz.close(); izlaz.close(); } }

246

8. P ROGRAMSKI UL AZ I IZL AZ

U ovom programu se pre samog c itanja i pisanja podataka najpre otvaraju ulazna i izlazna datoteka. Ukoliko se bilo koja od ovih datoteka ne moe otvoriti, nema smisla nastaviti s radom i program se zato zavrava u rukovaocima ove greke uz prethodno prikazivanje odgovaraju ce poruke. Ako je sve u redu, u programu se redom u citavaju brojevi u jedan niz, taj niz se sortira u rastu cem redosledu njegovih vrednosti i na kraju se ove vrednosti sortiranog niza redom upisuju u izlaznu datoteku. Ovaj postupak se izvrava unutar try bloka kako bi se otkrile mogu ce greke u radu sa nizom ili datotekama. Na primer, ukoliko ulazna datoteka sadri vie od 1000 bojeva kolika je duina niza, do ci c e do izbacivanja izuzetka usled prekora cenja granice nize. Ova greka se onda hvata u prvoj klauzuli catch iza greke opteg tipa RuntimeException. Primetimo i prakti cnu korisnost klauzule finally u programu, jer se naredbe unutar finally bloka obavezno izvravaju nezavisno od toga da li je u try bloku dolo do neke greke ili ne. Zbog toga se sa sigurno cu moe obezbediti da obe datoteke budu propisno zatvorene na kraju programa.

8.3 Imena datoteka


Neka datoteka kao celina za grupu odredenih podataka moe se zapravo posmatrati iz dva ugla. Jedan aspekt datoteke je njeno ime i drugi aspekt je njen sadraj. Sadraj datoteke c ine njeni podaci koji se mogu c itati i upisivati U/I operacijama o kojima je bilo re ci u prethodnom delu poglavlja. Ime datoteke je do sada uzimano zdravo za gotovo kao neki niz znakova, ali to je ipak malo sloenija tema. Klasa File u Javi je realizacija apstraktnog pojma imena datoteka kojom se omogu cava njihovo manipulisanje u sistemu datoteka na spoljanjem uredaju nezavisno od konkretnih detalja. Klasa File sadri me tode za odredivanje raznih svojstava datoteka, kao i metode za preimenovanje i brisanje datoteka. Konceptualno, svaka datoteka se nalazi u nekom direktorijumu u sistemu datoteka. Jednostavno ime datoteke kao to je podaci.txt odnosi se na datoteku koja se nalazi u takozvanom aktuelnom direktorijumu (ili radnom direktorijumu ili podrazumevanom direktorijumu). Svaki program na poc etku izvravanja ima pridruen aktuelni direktorijum koji je obi cno onaj u kome se nalazi sama datoteka sa bajtkodom programa. Aktuelni direktorijum medutim nije stalno svojstvo programa i moe se promeniti tokom iz vravanja, ali prosta imena datoteka uvek ukazuju na datoteke u aktuelnom

8.3. Imena datoteka

247

direktorijumu, ma koji on trenutno bio. Ukoliko se koristi datoteka koja nije u aktuelnom direktorijumu, mora se navesti njeno puno ime u obliku koji obuhvata njeno prosto ime i direktorijum u kome se datoteka nalazi. Puno ime datoteke, u stvari, moe se pisati na dva na cina. Apsolutno ime datoteke jedinstveno ukazuje na datoteku medu svim datotekama u sistemu datoteka. Kako je sistem datoteka hijerarhijski organizovan po direktorijumima, apsolutno ime datoteke odreduje mesto datoteke tako to se navodi putanja niza direktorijuma koja vodi od po cetnog direktorijuma na vrhu hijerarhije do same datoteke. Sa druge strane, relativno ime datoteke odreduje mesto datoteke tako to se navodi (obi cno kra ca) putanja niza direktorijuma koja vodi od aktuelnog direktorijuma do same datoteke. Na cin pisanja apsolutnog i relativnog imena datoteka zavisi donekle od operativnog sistema ra cunara, pa to treba imati u vidu prilikom pisanja programa kako bi se oni mogli izvravati na svakom ra cunaru. Razlike u pisanju imena datoteka najbolje je pokazati na primerima: podaci.txt je prosto ime datoteke u svakom sistemu. Ovim na cinom pisanja se odreduje da se datoteka nalazi u aktuelnom direktorijumu. /home/dzivkovic/java/podaci.txt je apsolutno ime datoteke u sistemu UNIX. Ovim na cinom pisanja se odreduje kompletan niz direk torijuma koje treba pre ci da bi se od po cetnog direktorijuma dolo do datoteke sa prostim imenom podaci.txt. Drugim re cima, ta datoteka se nalazi u direktorijumu java. Taj direktorijum se sa svoje strane nalazi u direktorijumu dzivkovic; direktorijum dzivkovic se nalazi u direktorijumu home; a direktorijum home se nalazi u po cetnom direktorijumu. C:\users\dzivkovic\java\podaci.txt je apsolutno ime datoteke u sistemu Windows. Mesto datoteke se odreduje na sli can na cin kao u prethodnom slu caju. Primetimo da se u sistemu Windows imena direktorijuma u nizu razdvajaju znakom obrnute kose crte, a ne znakom kose crte. java/podaci.txt je relativno ime datoteke u sistemu UNIX. Ovim nac inom pisanja se odreduje putanja niza direktorijuma koju treba pro c i da bi se od aktuelnog direktorijuma dolo do datoteke sa prostim imenom podaci.txt. Drugim re cima, ta datoteka se nalazi u direktorijumu java, a taj direktorijum se nalazi u aktuelnom direktorijumu. U sistemu Windows, isto relativno ime datoteke zapisuje se na sli can na cin: java\podaci.txt. Problem druga cijeg pisanja imena datoteka u razli citim sistemima nije toliko izraen u gra ckim programima, jer umesto pisanja imena datoteke

248

8. P ROGRAMSKI UL AZ I IZL AZ

korisnik moe eljenu datoteku izabrati na laki na cin putem gra ckog interfejsa. O ovome se vie govori u nastavku poglavlja. U programu je mogu ce dinami cki saznati apsolutna imena dva posebna direktorijuma koja su vana za odredivanje mesta datoteka: aktuelni direk torijum i korisnikov mati cni direktorijum. Ova imena pripadaju grupi sistemskih svojstava koja se mogu dobiti kao rezultat metoda getProperty() iz klase System: System.getProperty("user.dir") Rezultat je apsolutno ime aktuelnog direktorijuma u obliku stringa. System.getProperty("user.home") Rezultat je apsolutno ime korisnikovog mati cnog direktorijuma u obliku stringa. Na primer, izvravanjem dve naredbe
System.out.println(System.getProperty("user.dir")); System.out.println(System.getProperty("user.home"));

na ekranu se kao rezultat dobijaju dva stringa za aktuelni i mati cni direktorijum u trenutku izvravanja tih naredbi:
D:\My Stuff\Java\Programs\TestDir C:\Users\Dejan Zivkovic

Klasa File
Objekat koji pripada klasi File iz paketa java.io predstavlja ime datoteke. Sama datoteka (tj. njen sadraj) na koju se to ime odnosi moe, ali i ne mora postojati. U klasi File se imena direktorijuma logi cki ne razlikuju od imena datoteka tako da se obe vrste imena uniformno tretiraju. Klasa File sadri jedan konstruktor kojim se objekat te klase konstruie od imena tipa String:
public File(String ime)

Argument ovog konstruktora moe biti prosto ime, relativno ime ili apsolutno ime. Na primer, izrazom new File("podaci.txt") konstruie se objekat tipa File koji ukazuje na datoteku sa imenom podaci.txt u aktuelnom direktorijumu. Drugi konstruktor klase File slui za konstruisanje objekta te klase koji ukazuje na relativno mesto datoteke:
public File(File dir, String ime)

Prvi parametar ovog konstruktora predstavlja ime direktorijuma u kome se nalazi datoteka. Drugi parametar predstavlja prosto ime datoteke u tom direktorijumu ili relativnu putanju od tog direktorijuma do datoteke.

8.3. Imena datoteka

249

Klasa File sadri vie objektnih metoda kojima se obezbeduje lako mani pulisanje datotekama u sistemu datoteka na disku. Ako je datoteka promenljiva tipa File, onda se naj ce ce koriste ovi od tih metoda: cka vrednost ta cno ili neta cno za datoteka.exists() Rezultat je logi visno od toga da li postoji datoteka sa imenom predstavljenim objektom klase File na koji ukazuje promenljiva datoteka. cka vrednost ta cno ili ne datoteka.isDirectory() Rezultat je logi ta cno zavisno od toga da li objekat klase File na koji ukazuje promenljiva datoteka predstavlja direktorijum. datoteka.delete() Brie se datoteka (ili direktorijum) iz sistema datoteka, ukoliko postoji. Rezultat je logi cka vrednost ta cno ili neta cno zavisno od toga da li je operacija uklanjanja uspeno izvrena. datoteka.list() Ako promenljiva datoteka ukazuje na neki direktorijum, onda je rezultat niz tipa String[] c iji su elementi imena datoteka (ili direktorijuma) u tom direktorijumu. U suprotnom slu caju se kao rezultat vra ca vrednost null. Sve klase koje slue za c itanje i pisanje datoteka imaju konstruktore c iji je parametar tipa File. Na primer, ako je datoteka promenljiva tipa File koja ukazuje na ime tekstualne datoteke koju treba c itati, onda se objekat tekstualnog toka tipa FileReader za njeno c itanje moe konstruisati izrazom new FileReader(datoteka).

Primer: lista svih datoteka u direktorijumu


U primeru programa u nastavku prikazuje se lista svih datoteka (i direktorijuma) u zadatom direktorijumu.
import java.io.*; import java.util.*; public class ListDir { public static void main(String[] args) { String imeDir; File dir; String[] datoteke; Scanner tastatura; // // // // ime direktorijuma koje zadaje korisnik objekat tipa File za taj direktorijum niz imena datoteka u tom direktorijumu ime direktorijuma se u citava putem tastature

tastatura = new Scanner(System.in); System.out.print("Unesite ime direktorijuma: "); imeDir = tastatura.nextLine().trim();

250

8. P ROGRAMSKI UL AZ I IZL AZ

dir = new File(imeDir); if (!dir.exists()) System.out.println("Takav direktorijum ne postoji!"); else if (!dir.isDirectory()) System.out.println("To nije direktorijum!"); else { datoteke = dir.list(); System.out.println("Datoteke u direktorijumu \"" + dir + "\":"); for (int i = 0; i < datoteke.length; i++) System.out.println(" " + datoteke[i]); } } }

Izbor datoteka u gra ckim programima


U radu sa datotekama skoro uvek je potrebno obezbediti izbor eljenog imena datoteke (direktorijuma) za ulaz ili izlaz programa. Na primer, u programu iz prethodnog odeljka korisnik preko tastature zadaje ime direktorijuma c iji se sadraj prikazuje na ekranu. Nedostatak ovog na cina izbora datoteka u tekstualnim programima, pored toga to je to podlono grekama, jeste to da korisnik mora poznavati detalje pisanja imena datoteka za konkretni ra cunar. U gra ckim programima se izbor datoteka moe obezbediti na mnogo laki na cin za korisnika putem posebnih komponenti. U Javi se gra cki dijalog za izbor datoteke predstavlja klasom JFileChooser u biblioteci Swing. U prozoru za izbor datoteke se prikazuje lista datoteka i direktorijuma u nekom direktorijumu. Korisnik iz tog prozora moe onda lako miem izabrati jednu od prikazanih datoteka ili direktorijuma ili prelaziti iz jednog direktorijuma u drugi. Na slici 8.4 je prikazan izgled tipi cnog prozora za izbor datoteke. Naj ce ce kori ceni konstruktor klase JFileChooser je onaj koji nema nijedan parametar i onda se u prozoru za izbor datoteke po cetno prikazuje sadraj korisnikovog mati cnog direktorijuma:
public JFileChooser()

Druga dva konstruktora omogu cavaju da se u prozoru za izbor datoteke prikae po cetni direktorijum koji je razli cit od mati cnog direktorijuma:
public JFileChooser(File dir) public JFileChooser(String dir)

Konstruisani objekat klase JFileChooser koji predstavlja gra cki prozor za izbor datoteke ne prikazuje se odmah na ekranu, nego se mora pozvati

8.3. Imena datoteka

251

Slika 8.4: Gra cki dijalog za izbor datoteke.

metod kojim se to postie u eljenoj ta cki programa. U stvari, postoje dva metoda za prikazivanje prozora za izbor datoteke, jer postoje dve vrste takvih prozora: prozor za otvaranje datoteke (engl. open le dialog ) i prozor za c uvanje datoteke (engl. save le dialog ). Prozor za otvaranje datoteke slui za izbor postoje ce datoteke radi u citavanja podataka iz te datoteke u program. Prozor za c uvanje datoteke slui za izbor postoje ce ili nepostoje ce datoteke radi upisivanja podataka iz programa u tu datoteku. Metodi za prikazivanje ovih prozora su showOpenDialog() i showSaveDialog(). Obratite panju na to da se ovi metodi ne zavravaju dok korisnik ne izabere datoteku ili ne odustane od izbora. Prozor za izbor datoteke uvek ima drugi prozor za koji je vezan. Taj roditeljski prozor je obi cno sloenija gra cka komponenta c iji jedan deo c ini prozor za izbor datoteke. Roditeljski prozor se navodi kao argument metoda showOpenDialog() i showSaveDialog(). U prostim slu cajevima kada prozor za izbor datoteke logi cki nema roditeljski prozor navodi se argument null, ali se i onda zapravo formira nevidljiva roditeljska komponenta. caju celobrojnu vredMetodi showOpenDialog() i showSaveDialog() vra nost koja ukazuje na to ta je korisnik uradio s prikazanim prozorom za izbor datoteke. Ta vrednost je jedna od stati ckih konstanti koje su denisane u klasi JFileChooser: CANCEL_OPTION ERROR_OPTION APPROVE_OPTION

252

8. P ROGRAMSKI UL AZ I IZL AZ

Ako je vra cena konstanta JFileChooser.APPROVE_OPTION, onda je korisnik izabrao datoteku u prozoru i njena reprezentacija tipa File moe se dobiti pozivom metoda getSelectedFile() iz klase JFileChooser. U ostalim slu cajevima datoteka nije izabrana u prozoru iz nekog razloga (na primer, korisnik je moda odustao od izbora klikom na dugme Cancel). Zbog toga u programu uvek treba proveriti vra cenu vrednost metoda za prikazivanje prozora za izbor datoteke i prema rezultatu te provere treba granati dalju logiku programa. Klasa JFileChooser sadri i metode kojima se prozor za izbor datoteke moe dodatno kongurisati pre nego to se prikae na ekranu. Tako, metodom setDialogTitle(String naslov) odreduje se tekst koji se pojavljuje u naslovu prozora za izbor datoteke. Metod setSelectedFile(File ime) slui da se u polju za ime datoteke prikae datoteka koja se unapred bira ukoliko se druga cije ne izabere u prozoru za izbor datoteke. (Argument null ovog metoda ozna cava da se nijedna datoteka ne podrazumeva da je prethodno izabrana i zato se prikazuje prazno polje za ime datoteke.) U nastavku je prikazan tipi can postupak za c itanje neke tekstualne datoteke izabrane od strane korisnika:
// Biranje datoteke u prozoru za izbor JFileChooser prozorIzbora = new JFileChooser(); prozorIzbora.setDialogTitle("Izaberite datoteku za citanje"); prozorIzbora.setSelectedFile(null); // nita unapred nije izabrano int izbor = prozorIzbora.showOpenDialog(null); if (izbor != JFileChooser.APPROVE_OPTION) return; // korisnik odustao od izbora File datoteka = prozorIzbora.getSelectedFile(); // Otvaranje izabrane datoteke Scanner ulaz; try { ulaz = new Scanner(new FileReader(datoteka)); } catch (Exception e) { JOptionPane.showMessageDialog(null, "Greka prilikom otvaranja datoteke:\n" + e); return; } // Citanje podataka iz ulaznog toka i njihova obrada try { . . // Naredbe za citanje i obradu podataka . }

8.3. Imena datoteka

253

catch (Exception e) { JOptionPane.showMessageDialog(null, "Greka prilikom citanja datoteke:\n" + e); } finally { ulaz.close(); }

Postupak za izbor datoteke u koju treba upisivati podatke vrlo je sli can prethodnom postupku za c itanje podataka iz datoteke. Jedina razlika je u tome to se obi cno vri dodatna provera da li izabrana datoteka ve c postoji. Naime, ukoliko izabrana datoteka postoji, njen sadraj se zamenjuje upisanim podacima. Zbog toga korisnuku treba omogu citi da se predomisli u sluc aju pogreno izabrane datoteke, jer bi u suprotnom slu caju stari sadraj postoje ce datoteke bio bespovratno izgubljen. U nastavku je prikazan tipi can postupak za pisanje neke tekstualne datoteke izabrane od strane korisnika:
// Biranje datoteke u prozoru za izbor JFileChooser prozorIzbora = new JFileChooser(); prozorIzbora.setDialogTitle("Izaberite datoteku za pisanje"); prozorIzbora.setSelectedFile(null); // nita unapred nije izabrano int izbor = prozorIzbora.showOpenDialog(null); if (izbor != JFileChooser.APPROVE_OPTION) return; // korisnik odustao od izbora File datoteka = prozorIzbora.getSelectedFile(); // Proveravanje da li izabrana datoteka postoji if (datoteka.exists()) { // zameniti postoje cu datoteku? int odgovor = JOptionPane.showConfirmDialog(null, "Datoteka \"" + datoteka.getName() + "\" postoji.\nZameniti njen sadraj?", "Potvrdite zamenu datoteke", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); if (odgovor != JOptionPane.YES_OPTION) return; // korisnik ne eli zamenu postoje ce datoteke } // Otvaranje izabrane datoteke PrintWriter izlaz; try { izlaz = new PrintWriter(new FileWriter(datoteka)); } catch (Exception e) { JOptionPane.showMessageDialog(null, "Greka prilikom otvaranja datoteke:\n" + e); return; }

254

8. P ROGRAMSKI UL AZ I IZL AZ

// Pisanje podataka u izlazni tok try { . . // Naredbe za pisanje . } catch (Exception e) { JOptionPane.showMessageDialog(null, "Greka prilikom pisanja datoteke:\n" + e); } finally { izlaz.close(); }

Primer: kopiranje datoteke


Pravljenje identi cne kopije neke datoteke je c est zadatak na ra cunaru. To je razlog zato svaki operativni sistem ra cunara omogu cava da se to uradi na lak na cin, bilo odgovaraju com komandom preko tastature ili postupkom vuc enja mia koriste ci gra cki interfejs. U ovom primeru se prikazuje upravo Java program c iji je zadatak prepisivanje sadraja jedne datoteke u drugu. Taj program je pou can, jer mnoge operacije s datotekama slede istu optu logiku kopiranja jedne datoteke, osim to se podaci iz ulazne datoteke prethodno obraduju na neki poseban na cin pre nego to se upiu u izlaznu datoteku. Poto program treba da kopira bilo koju datoteku, ne moe se pretpostaviti da je originalna datoteka u tekstualnom obliku, nego se kopiranje mora izvriti bajt po bajt. Zbog toga se ne mogu koristiti tekstualni tokovi tipa Reader i Writer, nego osnovni binarni tokovi InputStream i OutputStream. Ako je original promenljiva koja ukazuje na ulazni tok tipa InputStream, onda se naredbom original.read() u citava jedan bajt iz tog ulaznog toka. Metod read() vra ca rezultat 1 kada su pro citani svi bajtovi iz ulaznog toga. S druge strane, ako je kopija promenljiva koja ukazuje na izlazni tok tipa OutputStream, onda se naredbom kopija.write(b) upisuje jedan bajt u taj izlazni tok. Prema tome, osnovni postupak za kopiranje datoteke sastoji se od slede ce obi cne petlje (koja se mora nalaziti unutar naredbe try, jer U/I operacije izbacuju proveravane izuzetke):
int b = original.read(); while(b >= 0) { kopija.write(b); b = original.read(); }

8.3. Imena datoteka

255

Da bi program bio to sli cniji pravim komandama operativnih sistema za kopiranje datoteke, ime originalne datoteke i ime njene kopije navode se kao argumenti programa. Ovi argumenti su zapravo niz stringova koji se programu prenose preko parametra glavnog metoda main(). Tako, ako taj parametar ima uobi cajeno ime args i ako je ime programa za kopiranje datoteke KopiDat, onda se u tom programu naredbom
java KopiDat orig.dat nova.dat

dobija vrednost elementa args[0] jednaka stringu "orig.dat" i vrednost elementa args[1] jednaka stringu "nova.dat". Broj elemenata niza args odreden cu polja args.length, kao to je to uobi cajeno za svaki niz. je vredno Da se nepanjom ne bi izgubio sadraj neke vane datoteke, u programu se kopiranje ne ce izvriti ukoliko je za ime izlazne datoteke navedena neka postoje ca datoteka. Medutim, da bi se kopiranje omogu cilo i u tom slu caju, mora se navesti dodatna opcija -u (to podse ca na uvek) kao prvi argument programa. Prema tome, naredbom
java KopiDat -u orig.dat nova.dat

dobila bi se kopija nova.dat od originalne datoteke orig.dat bez obzira na to da li ve c postoji neka stara datoteka nova.dat.
import java.io.*; public class KopiDat { public static void main(String[] args) { String imeOriginala; String imeKopije; InputStream original; OutputStream kopija; boolean uvek = false; int brojBajtova; // ukupan broj kopiranih bajtova // Odre divanje imena datoteka iz komandnog reda if (args.length == 3 && args[0].equalsIgnoreCase("-u")) { imeOriginala = args[1]; imeKopije = args[2]; uvek = true; } else if (args.length == 2) { imeOriginala = args[0]; imeKopije = args[1]; } else { System.out.println( "Upotreba: java KopiDat <original> <kopija>");

256

8. P ROGRAMSKI UL AZ I IZL AZ

System.out.println( " ili java KopiDat -u <original> <kopija>"); return; } // Konstruisanje ulaznog toka try { original = new FileInputStream(imeOriginala); } catch (FileNotFoundException e) { System.out.print("Ulazna datoteka \"" + imeOriginala + "\""); System.out.println(" ne postoji."); return; } // Proveravanje da li izlazna datoteka ve c postoji File datoteka = new File(imeKopije); if (datoteka.exists() && uvek == false) { System.out.println( "Izlazna datoteka postoji. Koristite -u za njenu zamenu."); return; } // Konstruisanje izlaznog toka try { kopija = new FileOutputStream(imeKopije); } catch (IOException e) { System.out.print("Izlazna datoteka \"" + imeKopije + "\""); System.out.println(" ne moe se otvoriti."); return; } // Bajt po bajt prepisivanje iz ulaznog toka u izlazni tok brojBajtova = 0; try { int b = original.read(); while(b >= 0) { kopija.write(b); brojBajtova++; b = original.read(); } System.out.print("Kopiranje zavreno: kopirano "); System.out.println(brojBajtova + " bajtova."); } catch (Exception e) { System.out.print("Neuspeno kopiranje (kopirano "); System.out.println(brojBajtova + " bajtova)."); System.out.println("Greka: " + e);

8.3. Imena datoteka

257

} finally { try { original.close(); kopija.close(); } catch (IOException e) { } } } }

G LAVA 9

P ROGRAMSKE NITI

Ra cunari obavljaju vie razli citih zadataka u isto vreme i ta njihova mogu cnost se naziva multitasking . Ovaj na cin rada nije teko zamisliti kod rac unara sa vie procesora, jer se razli citi zadaci mogu paralelno izvravati na odvojenim procesorima. Ali i ra cunari sa jednim procesorom mogu raditi multitasking, dodue ne bukvalno nego simuliraju ci paralelnost naizmeni cnim izvravanjem vie zadataka po malo. Analogno ra cunarima, zadatak jednog programa u Javi se moe podeliti u vie zadataka koji se paralelno izvravaju. Jedan zadatak koji se paraleno izvrava s drugim zadacima u okviru programa naziva se nit izvravanja ili kra ce samo nit (engl. thread), pa se ova mogu cnost Java programa naziva multithreading . U ovom poglavlju se govori o osnovnim konceptima u vezi sa nitima izvravanja, kao i o tome kako se piu programi sa vie niti u Javi. Naalost, paralelno programiranje je jo tee od obi cnog (jednonitnog) programiranja, jer kada se vie niti izvrava istovremeno, njihov efekat na stanje programa je mnogo tee pratiti i mogu ce se potpuno nove kategorije greaka.

9.1 Osnovni pojmovi


Nit je sled izvavanja nekog zadatka, od po cetka do kraja, unutar programa. Zadatak je programska celina koja se izvrava nezavisno od drugih delova programa. Nit je dakle programski mehanizam kojim se obezbeduje nezavisno izvravanje jednog zadatka u okviru programa. U jednom Java programu se moe pokrenuti vie niti radi paralelnog izvravanja. Vie niti se u multiprocesorskom sistemu mogu izvravati istovremeno, dok se u jednoprocesorskom sistemu moe dobiti privid njihovog jednovremenog izvravanja. Na cin na koji se vie niti mogu izvravati ilustrovan je na slici 9.1.

260

9. P ROGRAMSKE NITI

Procesor 1 nit 1 Procesor 2 nit 2 Procesor 3 nit 3 (a) nit 3 nit 2 nit 1

Procesor 1

(b)

Slika 9.1: Izvravanje tri niti na multiprocesorskom i jednoprocesorskom rac unaru.

Kao to pokazuje slika 9.1(b), vie niti u jednoprocesorskom sistemu dele vreme jedinog procesora tako to ih procesor izvrava malo po malo. Na taj na cin se prakti cno dobija privid paralelnog izvravanja zadataka, naro cito ako su oni po prirodi raznovrsni. Na primer, procesor ne radi nita dok se u jednom programu c eka da korisnik unese neke podatke preko tastature, pa za to vreme procesor moe izvravati neki drugi zadatak kao to je, recimo, crtanje nekog prozora na ekranu. Vie niti u programu doprinose ve coj ivosti i interaktivnosti programa. Tako, recimo, svaki dobar program za unos dokumenata omogu cava da se tampa ili sa cuva neki dokument na disku dok se istovremeno kuca na tastaturi, jer se sastoji od dve-tri niti u kojima se posebno tampa dokument, upisuje datoteka na disku i u citavaju znakovi uneseni preko tastatute ili izvravaju opcije izabrane miem. Svaki Java program poseduje bar jednu nit izvravanja: kada se pokrene program, JVM automatski formira nit u kojoj se izvrava metod main() glavne klase. U ovoj glavnoj niti se zatim mogu pokrenuti druge niti radi paralelnog izvravanja. Primetimo da se izvravanje tih niti moe nastaviti c ak i posle zavretka glavne niti. Gra cki Java program poseduje bar jo jednu dodatnu nit izvravanja u kojoj se odvija rukovanje dogadajima i crtanje komponenti na ekranu. Ova gra cka nit se pokre ce c im se konstruie prvi gra cki okvir u programu i ona se izvrava paralelno s glavnom niti. U Javi, nit izvravanja se predstavljena objektom koji pripada klasi Thread (ili klasi koja nasleduje ovu klasu) iz paketa java.lang. Svrha ovog objekta je da izvri odreden zadatak koji je obuhva cen jednim metodom. Ta cnije, jedan zadatak je instanca interfejsa Runnable u kojem je denisan samo jedan metod koji se izvrava u posebnoj niti. Kada se izvravanje tog metoda zavri, bilo regularno ili usled nekog neuhva cenog izuzetka, zavrava se i izvravanje

9.2. Zadaci i niti za paralelno izvravanje

261

njegove niti. Zavrena nit se vie ne moe aktivirati, a ni njen objekat se vie ne moe koristiti za pokretanje nove niti.

9.2 Zadaci i niti za paralelno izvravanje


Da bi se odredio zadatak koji se moe paralelno izvravati, mora se najpre denisati klasa takvog zadatka. Zadaci su u Javi objekti, kao i sve ostalo, ali njihova klasa mora implementirati interfejs Runnable kako bi se zadaci mogli paralelno izvravati u nitima. Interfejs Runnable je prili cno jednostavan i sadri samo metod public void run(). Ovaj metod se mora implementirati u klasi zadatka kako bi se ukazalo da se zadatak izvrava u zasebnoj niti i da bi se denisala funkcionalnost zadatka. Osnovni ablon za denisanje klase zadataka ima dakle slede ci oblik:
// ablon za klasu zadatka koji se paralelno izvrava public class KlasaZadatka implements Runnable { . . . // Konstruktor za konstruisanje instance zadatka public KlasaZadatka(...) { . . . } // Implementacija metoda run() iz interfejsa Runnable public void run() { // Naredbe za izvravanje zadatka . . . } . . . }

Konkretan zadatak c ija je klasa denisana prema ovom ablonu moe se zatim konstruisati na uobi cajen na cin:
KlasaZadatka zadatak = new KlasaZadatka(...);

Zadatak se mora izvriti u niti. Klasa Thread sadri konstruktore za konstruisanje niti i mnoge korisne metode za kontrolisanje niti. Da bi se konstruisala nit za izvravanje prethodno konstruisanog zadatka, moe se pisati na primer:
Thread nit = new Thread(zadatak);

Medutim, konstruisanjem niti se automatski ne zapo cinje izvravanje od govaraju ceg zadatka. To se mora eksplicitno uraditi u odgovaraju coj ta cki programa, odnosno nakon konstruisanja niti mora se koristiti metod start() iz klase Thread radi otpo cinjanja izvravanja zadatka u niti:

262

9. P ROGRAMSKE NITI

nit.start();

Primetimo da se ovim zapravo po cinje izvravanje metoda run() koji je implementiran u klasi zadatka. Obratite panju i na to da se ovaj metod implicitno poziva od strane JVM ako se metod run() pozove direktno u programu, on c e se izvriti u istoj niti u kojoj se izvrava njegov poziv, bez formiranja nove niti! Kompletan ablon za ceo postupak moe se prikazati na slede ci na cin:
// ablon za izvravanje zadatka u niti public class NekaKlasa { . . . public void nekiMetod(...) { . . . // Konstruisanje instance klase KlasaZadatka KlasaZadatka zadatak = new KlasaZadatka(...); // Konstruisanje niti za izvravanje te instance Thread nit = new Thread(zadatak); // Startovanje niti za izvravanje zadatka nit.start(); . . . } . . . }

Primer: tri paralelne niti


U ovom primeru je prikazan program u kojem se tri zadatka paralelno izvravaju u svojim nitima: Prvi zadatak prikazuje tekst Java 20 puta. Drugi zadatak prikazuje tekst C++ 20 puta. Tre ci zadatak prikazuje brojeve od 1 do 20. Poto prvi i drugi zadatak imaju sli cnu funkcionalnost, predstavljeni su jednom klasom PrikazTeksta. Ova klasa implementira interfejs Runnable denisanjem metoda run() za prikaz teksta odreden ci zada broj puta. Tre tak je predstavljen klasom PrikazBrojeva koja takode implementira interfejs Runnable denisanjem metoda run(), ali za prikaz niza brojeva od 1 do neke granice.
public class TriNiti {

9.2. Zadaci i niti za paralelno izvravanje

263

public static void main(String[] args) { // Konstruisanje tri zadatka Runnable java = new PrikazTeksta("Java",20); Runnable cpp = new PrikazTeksta("C++", 20); Runnable niz = new PrikazBrojeva(20); // Konstruisanje tri niti izvravanja Thread nitJava = new Thread(java); Thread nitCpp = new Thread(cpp); Thread nitNiz = new Thread(niz); // Startovanje niti nitJava.start(); nitCpp.start(); nitNiz.start(); } } // Zadatak za prikaz teksta odre den broj puta class PrikazTeksta implements Runnable { private String tekst; // tekst koji se prikazuje private int n; // broj puta koliko se prikazuje // Konstruktor zadatka public PrikazTeksta(String tekst, int n) { this.tekst = tekst; this.n = n; } // Implementacija metoda run() public void run() { for (int i = 0; i < n; i++) { System.out.print(tekst + " "); } } } // Zadatak za prikaz niza brojeva od 1 do neke granice class PrikazBrojeva implements Runnable { private int n; // granica niza brojeva koji se prikazuju

// Konstruktor zadatka public PrikazBrojeva(int n) { this.n = n; }

264

9. P ROGRAMSKE NITI

// Implementacija metoda run() public void run() { for (int i = 1; i <= n; i++) { System.out.print(i + " "); } } }

Ako bi se ovaj program izvrio na troprocesorskom ra cunaru, sve tri niti bi se izvravale istovremeno. Ali ukoliko bi se ovaj program izvrio na jednoprocesorskom ra cunaru, tri niti bi delile vreme jedinog procesora i naizmeni cno bi se prikazivao tekst i brojevi na ekranu. Na slici 9.2 je prikazan jedan mogu ci rezultat izvravanja programa na jednoprocesorskom ra cunaru.

Slika 9.2: Rezultat izvravanja tri niti na jednoprocesorskom ra cunaru.

Klasa Thread
Pored metoda start() kojim se zapo cinje izvravanje niti, odnosno kojim se zapravo aktivira metod run() odgovaraju ceg zadatka radi izvravanja u niti, klasa Thread sadri i metode kojima se moe dodatno kontrolisati izvravanje neke niti. U stvari, klasa Thread implementira interfejs Runnable tako da se zajedno mogu denisati neka nit i zadatak koji se u njoj izvrava. Naime, ovaj drugi na cin za programiranje niti sastoji se od denisanja jedne klase koja nasleduje klasu Thread i ujedno implementiranja metoda run() u toj novoj klasi. Ovaj metod run() denie zadatak koji c e se izvriti u niti, odnosno kada se objekat niti nove klase startuje, taj metod run() c e se izvriti

9.2. Zadaci i niti za paralelno izvravanje

265

u posebnoj niti. ablon za drugi na cin programiranja niti ima dakle slede ci oblik:
// ablon za definisanje klase niti i zadatka zajedno public class NekaNit extends Thread { . . . // Konstruktor public NekaNit(...) { . . . } // Implementacija metoda run() iz interfejsa Runnable public void run() { // Naredbe za izvravanje zadatka . . . } . . . }

Ovako denisana nit kao i zadatak koji se u njoj izvrava mogu se zatim koristiti otprilike na slede ci na cin:
// ablon za izvravanje niti public class NekaKlasa { . . . public void nekiMetod(...) { . . . // Konstruisanje jedne niti NekaNit nit1 = new NekaNit(...); // Startovanje te niti nit1.start(); . . . // Konstruisanje druge niti NekaNit nit2 = new NekaNit(...); // Startovanje te niti nit2.start(); . . . } . . . }

Iako ovaj drugi na cin za programiranje niti izgleda kompaktniji, takav pristup obi cno nije dobar jer se u njemu meaju pojmovi zadatka i mehanizma za izvravanje tog zadatka. Zbog toga je u programu obi cno bolje odvojiti deniciju zadatka od niti u kojoj se izvrava.

266

9. P ROGRAMSKE NITI

Nepotpun spisak metoda u klasi Thread kojima se moe kontrolisati izvravanje niti je: boolean isAlive() static void sleep(long milisec) void interrupt() static void yield() void join() void setPriority(int p) Metod isAlive() se koristi za proveru statusa izvravanja neke niti. Ako je t objekat tipa Thread, onda t.isAlive() kao rezultat daje ta cno ili neta cno prema tome da li je nit t iva. Nit je iva izmedu trenutka njenog startova nja i trenutka njenog zavretka. Nakon zavretka niti se kae da je ona mrtva. Stati cki metod sleep() prevodi nit u kojoj se taj metod izvrava u stanje spavanja za navedeni broj milisekundi. Nit koja je u stanju spavanja je i dalje iva, ali se ne izvrava nego je blokirana. Dok se neka nit nalazi u stanju spavanja, ra cunar moe da izvrava neku drugu nit istog programa ili potpuno drugi program. Metod sleep() koristi se radi privremenog prekida izvravanja neke niti i nastavljanja njenog izvravanja posle isteka navedene duine vremena. Ovaj metod moe izbaciti proveravani izuzetak tipa InterruptedException koji zahteva obavezno rukovanje. U praksi to zna ci da se metod sleep() obi cno pie unutar naredbe try u kojoj se hvata potencijalni izuzetak tipa InterruptedException:
try { Thread.sleep(duinaPauze); } catch (InterruptedException e) { }

Na primer, ako se u primeru tri niti na strani 262 izmeni metod run() za zadatak PrikazBrojeva na slede ci na cin:
public void run() { try { for (int i = 1; i <= n; i++) { System.out.print(i + " "); if (i > 10) Thread.sleep(1); } } catch (InterruptedException e) { } }

9.2. Zadaci i niti za paralelno izvravanje

267

onda se nakon prikazivanja svakog broja ve ceg od 10 izvravanje niti nitNiz prekida za (najmanje) jednu milisekundu. Jedna nit moe drugoj niti poslati signal prekida da druga nit prekine ono to trenutno radi i da uradi neto sasvim drugo. Na primer, ukoliko je nit u stanju spavanja ili je blokirana usled nekog drugog razloga, to moe biti signal da se nit probudi i uradi neto to zahteva hitni intervenciju. Jedna nit alje signal prekida drugoj niti t pozivanjem njenog metoda t.interrupt(). Detaljan opis mehanizma prekidanja niti prevazilazi okvire ove knjige, ali ipak treba znati da nit koja se prekida od strane druge niti mora biti pripremljena da reaguje na poslati signal prekida. Stati cki metod yield() je indikacija za JVM da je nit u kojoj se taj metod izvrava spremna da prepusti procesor drugim nitima radi izvravanja. Na primer, ako se u primeru tri niti na strani 262 izmeni metod run() za zadatak PrikazBrojeva na slede ci na cin:
public void run() { try { for (int i = 1; i <= n; i++) { System.out.print(i + " "); Thread.yield(); } } catch (InterruptedException e) { } }

onda se nakon prikazivanja svakog broja izvravanje niti nitNiz prekida tako da se iza svakog broja prikazuje neki tekst drugom niti. Obratite ipak panju na to da JVM nije obavezna da potuje ove lepe manire neke niti i da po svom nahodenju moe ignorisati poziv metoda yield() u niti i nastaviti njeno izvravanje bez prekida. Zbog toga se ovaj metod retko koristi u praksi, osim za testiranje i reavanje problema neekasnosti u programu radi otklanjanja uskih grla. U nekim slu cajevima je potrebno da jedna nit sa ceka da se druga nit zavri i da tek onda prva nit nastavi izvravanje. Ova funkcionalnost se moe posti ci metodom join() iz klase Thread. Ako je t jedna nit tipa Thread i u drugoj niti se izvrava t.join(), onda ova druga nit prelazi u stanje spavanja dok se nit t ne zavri. Ako je nit t ve c mrtva, onda poziv t.join() nema efekta, odnosno nit u kojoj se izvrava taj poziv nastavlja izvravanje bez prekida. Metod join() moe izbaciti izuzetak tipa InterruptedException c ije je rukovanje obavezno u programu. Radi ilustracije, primetimo da se u primeru tri niti na strani 262 sve niti zapravo pokre cu u c etvrtoj, glavnoj niti u kojoj se izvrava metod main().

268

9. P ROGRAMSKE NITI

Nakon toga se glavna nit odmah zavrava, skoro sigurno pre ostalih niti. Zato ukoliko elimo da recimo prikaemo ukupno vreme izvravanja sve tri niti, to moramo uraditi na slede ci na cin:
public static void main(String[] args) { // Konstruisanje tri zadatka Runnable java = new PrikazTeksta("Java",20); Runnable cpp = new PrikazTeksta("C++", 20); Runnable niz = new PrikazBrojeva(20); // Konstruisanje tri niti izvravanja Thread nitJava = new Thread(java); Thread nitCpp = new Thread(cpp); Thread nitNiz = new Thread(niz); long po cetak = System.currentTimeMillis(); // Startovanje niti nitJava.start(); nitCpp.start(); nitNiz.start(); try { nitJava.join(); // cekanje (spavanje) dok se nitJava ne zavri nitCpp.join(); // cekanje (spavanje) dok se nitCpp ne zavri nitNiz.join(); // cekanje (spavanje) dok se nitNiz ne zavri } catch (InterruptedException e) { } // U ovoj ta cki su sve tri niti zavene System.out.println(); long ukupnoVreme = System.currentTimeMillis() - po cetak; System.out.print("Ukupno vreme izvravanja tri niti: "); System.out.println((ukupnoVreme/1000.0) + " sekundi."); }

Izvravanje ove verzije metoda main() ilustrovano je na slici 9.3. Primetimo da ovaj metod ne daje o cekivani rezultat ukoliko u njemu neki od metoda join() izbaci izuzetak tipa InterruptedException. Takva mogu cnost, ma kako ona mala bila, mora se preduprediti i obezbediti sigurno registrovanje zavretka neke niti otprilike na slede ci na cin:
while (nitJava.isAlive()) { try { nitJava.join(); }

9.2. Zadaci i niti za paralelno izvravanje

269

catch (InterruptedException e) { } }

main

nitJava

nitCpp

nitNiz

nitJava.join() nitCpp.join()

zavretak

zavretak
nitNiz.join()

zavretak

zavretak

Slika 9.3: Cekanje na zavretak tri niti upotrebom metoda join(). JVM dodeljuje svakoj niti prioritet za izvravanje. Neka nit t na po cetku nasleduje prioritet one niti u kojoj je nit t startovana. Prioritet niti moe se menjati metodom setPriority(), a aktuelna vrednost prioriteta niti moe se dobiti metodom getPriority(). Prioriteti su celi brojevi od 1 do 10, a klasa Thread sadri konstante MIN_PRIORITY, NORM_PRIORITY i MAX_PRIORITY koje odgovaraju prioritetima 1, 5 i 10. Glavna nit na po cetku dobija prioritet Thread.NORM_PRIORITY. Od niti koje su spremne za izvravanje, JVM radi izvravanja uvek bira onu koja ima najvii prioritet. Ukoliko nekoliko spremnih niti imaju isti prioritet, one se na jednom procesoru izvravaju na kruni na cin. Niti nieg prioriteta mogu se izvravati samo ukoliko nema spremnih niti vieg prioriteta. Na primer, ako se nakon startovanja tri niti u prethodnom primeru metoda main() doda naredba
nitJava.setPriority(Thread.MAX_PRIORITY);

onda se prva zavrava nit nitJava.

Primer: animirani tekst


Mehanizam paralelnog izvravanja niti moe se iskoristiti za kontrolisanje animacije. U ovom primeru se postie efekat trep cu ceg teksta tako to se tekst naizmeni cno prikazuje i brie u oznaci tipa JLabel gra ckog programa. Usporen rad ovog programa ilustrovan je na slici 9.4.

270

9. P ROGRAMSKE NITI

Slika 9.4: Efekat treptanja teksta Dobrodoli.

import javax.swing.*; public class TestAnimiraniTekst { public static void main(String[] args) { // Konstruisanje okvira JFrame okvir = new JFrame("Animirani tekst"); okvir.setSize(300, 200); okvir.setLocation(100, 150); okvir.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); okvir.add(new AnimiraniTekst()); okvir.setVisible(true); } } class AnimiraniTekst extends JPanel implements Runnable { private JLabel oznaka = new JLabel("Dobrodoli", JLabel.CENTER); public AnimiraniTekst() { add(oznaka); new Thread(this).start(); } // Prikazati ili obrisati tekst svakih 500 milisekundi public void run() { try { while (true) { if (oznaka.getText() == null) oznaka.setText("Dobrodoli"); else oznaka.setText(null); Thread.sleep(500); } }

9.3. Sinhronizacija niti

271

catch (InterruptedException ex) { } } }

Klasa AnimiraniTekst implementira interfejs Runnable i zato predstavlja klasu zadatka koji se moe paralelno izvravati u niti. U konstruktoru se konstruie nit za ovaj zadatak i odmah se ta nit startuje. Metod run() odreduje kako se zadatak izvrava u niti: tekst u oznaci se upisuje ukoliko oznaka ne sadri tekst, a tekst u oznaci se brie ukoliko oznaka sadri tekst. Na taj na cin se zapravo simulira treptanje teksta, ukoliko se upisivanje i brisanje teksta obavljaju dovoljno brzo (ali ne prebrzo).

9.3 Sinhronizacija niti


Programiranje vie niti u kojima se paralelno izvravaju nezavisni zadaci nije toliko komplikovano. Prava teko ca nastaje kada niti moraju da uzajamno komuniciraju radi dobijanja kona cnog rezultata programa. Jedan na cin na koji niti mogu da medusobno saraduju je deljenjem ra cunarskih resursa kao to su promenljive ili prozori na ekranu. Ali ako dve ili vie niti moraju da koriste isti resurs, onda se mora obratiti panja na njihovo programiranje kako ne bi koristile taj resurs u isto vreme. Da bismo ilustrovali ovaj problem, razmotrimo najobi cniju naredbu dodele kojom se uve ceva neki broja c za jedan:
broja c = broja c + 1;

Izvravanje ove naredbe izvodi se, u stvari, u tri koraka: c 1. Citanje vrednosti promenljive broja 2. Izra cunavanje zbira te vrednosti i broja 1 3. Upisivanje rezultata u promenljivu broja c Ako dve niti izvravaju ovu naredbu redom jedna iza druge, onda c e efekat toga biti uve cana vrednost broja ca za 2. Medutim, ako dve niti izvravaju ovu istu naredbu u isto vreme, onda efekat toga moe biti neo cekivan. Naime, pretpostavimo da je jedna nit zavrila samo prva dva od prethodna tri koraka kada je bila prekinuta i da je druga nit po cela da izvrava sva tri koraka. Poto u prvoj niti nova vrednost broja ca nije bila upisana, u drugoj niti c e biti pro citana stara vrednost broja ca i ona c e biti uve cana i upisana kao nova vrednost broja ca. Kada prva nit nastavi sa izvravanjem, opet c e stara vrednost broja ca uve cana za jedan biti upisana kao nova vrednost broja ca. Prema tome, efekat

272

9. P ROGRAMSKE NITI

ovog mogu ceg na cina paralelnog izvravanja bi ce vrednost broja ca uve cana samo za 1, a ne za 2! Ova vrsta greke kod paralelnog izvravanja naziva se stanje trke (engl. race condition). Ona nastaje kada je jedna nit prekinuta u sredini izvravanja neke sloene operacije od vie koraka, a druga nit moe promeniti neku vrednost ili uslov od kojih zavisi izvravanje prve niti. Termin za ovu vrstu greke poti ce od toga to je prva nit u trci da zavri sve korake pre nego to bude prekinuta od strane druge niti. Da bismo ilustrovali problem trke niti, u narednom programu se konstruie broja c b sa po cetnom vredno cu 0, kao i 10 niti (zadataka) u kojima se tom broja cu dodaje jedinica. Nakon zavretka paralelnog izvravanja svih niti prikazuje se vrednost broja ca, koja bi naravno trebalo da bude 10 da nema problema trke niti. Medutim, kako sve niti istovremeno menjaju isti broja c, rezultat je nepredvidljiv. Tako, jedan pogrean rezultat izvravanja ovog programa prikazan je na slici 9.5.
public class Brojanje { public static void main(String[] args) { Broja c b = new Broja c(); Thread[] niti = new Thread[10]; for (int i = 0; i < niti.length; i++) { niti[i] = new Dodavanje1(b); niti[i].start(); } for (int i = 0; i < niti.length; i++) { try { niti[i].join(); } catch (InterruptedException e) { } } // U ovoj ta cki su sve niti zavrene System.out.println("Ukupna vrednost broja ca: " + b.vrednost()); } } class Dodavanje1 extends Thread implements Runnable { private Broja c b; public Dodavanje1(Broja c b) { this.b = b; } public void run() {

9.3. Sinhronizacija niti

273

b.dodaj1(); } } class Broja c { private int broja c = 0; public void dodaj1() { // Naredba broja c = broja c + 1 je namerno razbijena na svoje // sastavne delove kako bi se istakao problem trke. Taj problem // je dodatno poja can slu cajnom zadrkom niti od 1 ms. int n = broja c; n = n + 1; if (Math.random() < 0.5) { try { Thread.sleep(1); } catch (InterruptedException ex) { } } broja c = n; } public int vrednost() { return broja c; } }

Slika 9.5: Problem trke niti je uzrok pogrenog brojanja. cem Drugi slu caj greke trke niti moe nastati u if naredbi, recimo u slede

274

9. P ROGRAMSKE NITI

primeru u kome se izbegava deljenje s nulom:


if (a != 0) c = b / a;

Ako se ova naredba izvrava u jednoj niti i vrednost promenljive a mogu promeniti vie niti, onda moe do ci do stanja trke ukoliko se ne preduzmu odredene mere za spre cavanje tog problema. Naime, moe se desiti da neka druga nit dodeli vrednost nula promenljivoj a u momentu izmedu trenutka kada se u prvoj niti proverava uslov a != 0 i trenutka kada se stvarno obavlja deljenje c = b / a. To zna ci da u prvoj niti moe do ci do greke deljenja sa nulom, iako se u toj niti neposredno pre deljenja provera da promenljiva a nije jednaka nuli! Da bi se reio problem trke niti koje se paralelno izvravaju, mora se u vienitnom programu obezbediti da jedna nit dobije ekskluzivno pravo da koristi deljeni resurs u odredenom trenutku. Taj mehanizam uzajamne isklju civosti u Javi realizovan je preko sinhronizovanih metoda i sinhronizovanih naredbi. Deljeni resurs u metodu ili blok naredbi titi se tako to se obezbeduje da se izvravanje sinhronizovanog metoda ili naredbe u jednoj niti ne moe prekinuti od strane neke druge niti. Problem trke niti u primeru brojanja moe se reiti ukoliko se metodi za uve cavanje broja ca i dobijanje njegove vrednosti u klasi Broja c deniu da budu sinhronizovani. U tu svrhu je dovoljno u zaglavlju ovih metoda na poc etku dodati samo modikator synchronized:
class SinhroniBroja c { private int broja c = 0; public synchronized void dodaj1() { broja c = broja c + 1; } public synchronized int vrednost() { return broja c; } }

Ako je broja c b tipa SinhroniBroja c, onda se u svakoj niti za dodavanje 1 tom broja cu moe izvriti b.dodaj1() na potpuno bezbedan na cin. Cinjenica da je metod dodaj1() sinhronizovan zna ci to da se samo u jednoj niti moe izvravati ovaj metod od po cetka do kraja. Drugim re cima, kada se u jednoj niti po cne izvravanje ovog metoda, nijedna druga nit ne moe po ceti da izvrava isti metod dok se on ne zavri u niti u kojoj je prvo po celo njegovo izvravanje.

9.3. Sinhronizacija niti

275

Ovim se spre cava mogu cnost istovremenog menjanja zajedni ckog broja ca u programu za brojanje i sada je njegov rezultat uvek ta can, kao to to pokazuje slika 9.6.

Slika 9.6: Problem trke niti je reen sinhronizovanim metodima.


c sutinski Primetimo da reenje problema trke niti u klasi SinhroniBroja zavisi od toga to je broja c privatna promenljiva. Na taj na cin se obezbeduje da svaki pristup toj promenljivoj mora i ci preko sinhronizovanih metoda u klasi SinhroniBroja c. Da je broja c bila javna promenljiva, onda bi neka nit mogla da zaobide c++. Time sinhronizaciju, recimo, naredbom b.broja bi se opet otvorila mogu cnost stanja trke u programu, jer bi jedna nit mogla promeniti vrednost broja ca istovremeno dok druga nit izvrava b.dodaj1(). Ovo pokazuje da sinhronizacija ne garantuje ekskluzivan pristup deljenom resursu, nego samo obezbeduje uzajamnu isklju civost izmedu onih niti u kojima se potuje princip sinhronizacije radi pristupa deljenom resursu. Klasa SinhroniBroja c ne spre cava sva mogu ca stanja trke koja mogu nastati kada se koristi neki broja c b te klase. Uzmimo na primer da je za ispravan rad nekog metoda potrebno da vrednost broja ca b bude jednaka nuli. To moemo pokuati da obezbedimo ovom if naredbom:
if (b.vrednost() == 0) nekiMetod();

U ovom slu caju ipak moe do ci do stanja trke u programu ukoliko se u drugoj niti uve ca vrednost broja ca izmedu trenutka kada se u prvoj niti proverava uslov b.vrednost() == 0 i trenutka kada se izvrava nekiMetod(). Drugim re cima, za pravilan rad programa, prvoj niti potreban je ekskluzivni pristup broja cu b za vreme izvravanja cele if naredbe. Problem u ovom

276

9. P ROGRAMSKE NITI

slu caju je to to se sinhronizacijom metoda u klasi SinhroniBroja c dobija ekskluzivni pristup broja cu b samo za vreme izvravanja b.vrednost(). Ovaj problem se moe reiti tako to se if naredba napie unutar sinhronizovane naredbe:
synchronized (b) { if (b.vrednost() == 0) nekiMetod(); }

Obratite panju na to da je u ovom slu caju objekat b neka vrsta argumenta sinhronizovane naredbe. To nije slu cajno, jer u optem obliku sintaksa sinhronizovane naredbe je:
synchronized (objekat) { naredbe }

Sinhronizacija niti putem uzajamne isklju civosti u Javi uvek se obavlja na osnovu nekog objekta. Ova c injenice se kolokvijalno izraava terminom da se vri sinhronizacija po nekom objektu: na primer, prethodna if naredba je sinhronizovana po b. Kao to se kod sinhronizovane naredbe neki niz naredbi sinhronizuje po objektu,1 tako se i sinhronizovani metodi implicitno sinhronizuju po objektu. Naime, objektni (nestati cki) metodi, kao to su oni u klasi SinhroniBroja c, sinhronizuju se po objektu za koji se pozivaju. U stvari, dodavanje modikatora synchronized nekom objektnom metodu ekvivalentno je pisanju tela tog metoda unutar sinhronizovane naredbe sa argumentom this. Na primer, sinhronizovani metod pod (a) na slede coj slici je ekvivalentan metodu pod (b): (a)
public synchronized void sMetod() { . . // Telo metoda . }

(b)
public void sMetod() { synchronized (this) { . . // Telo metoda . } }

Stati cki metodi takode mogu biti sinhronizovani. Oni se sinhronizuju po specijalnom objektu koji predstavlja klasu koja sadri stati cki metod.
1 Blok naredba unutar sinhronizovane naredbe se naziva i kriti cna oblast .

9.3. Sinhronizacija niti

277

Opte pravilo u Javi za sinhronizaciju niti je dakle da se u dvema nitima ne moe istovremeno izvravati blok naredbi koji je sinhronizovan po istom objektu. Ako je jedna nit sinhronizovana po nekom objektu i druga nit pokuava da se sinhronizuje po istom objektu, onda je druga nit primorana da c eka dok prva nit ne zavri sa sinhronizuju cim objektom. Realizacija ovog na cina sinhronizacije zasnovana je na modelu katanca (engl. lock). Naime, pored uobi cajenih polja i metoda, svaki objekat u Javi sadri i jedan katanac koji u svakom trenutku moe dobiti samo jedna nit. Da bi se u nekoj niti izvrila sinhronizovana naredba ili sinhronizovani metod, ta nit mora dobiti katanac sinhronizuju ceg objekta. Ako je taj katanac trenutno slobodan, onda ga nit zauzima i odmah po cinje sa izvravanjem. Nakon zavetka izvravanja sinhronizovane naredbe ili sinhronizovanog metoda, nit oslobada ceg objekta. Ako druga nit pokua da zauzme katanac sinhronizuju katanac koji ve c dri prva nit, onda druga nit mora da c eka dok prva nit ne oslobodi katanac. U stvari, druga nit prelazi u stanje spavanja i ne ce biti probudena sve dok katanac ne bude na raspolaganju. Ovaj postupak je ilustrovan na slici 9.7.
Nit 1 Zauzimanje katanca sinhronizuju ceg objekta Izvravanje sinhronizovanog programskog koda Cekanje na katanac Nit 2

Oslobadanje katanca Zauzimanje katanca sinhronizuju ceg objekta Izvravanje sinhronizovanog programskog koda

Oslobadanje katanca

Slika 9.7: Sinhronizacija niti pomo cu katanca.

Mrtva petlja
Sinhronizacijom se reava problem trke niti, ali se i uvodi mogu cnost pojave druge vrste greke koja se naziva mrtva petlja (engl. deadlock). Mrtva

278

9. P ROGRAMSKE NITI

petlja nastaje kada jedna nit bezuspeno c eka na neki resurs koji nikada ne moe dobiti. Do mrtve petlje moe do ci kada se niti sinhronizuju po nekoliko istih objekata. Onda se moe desiti da, recimo, dve niti zauzimaju katance dva razli cita objekta i obe niti c ekaju da druga nit oslobodi katanac drugog objekata kako bi mogle da nastave rad. Razmotrimo podrobnije slede ci primer dve niti i dva sinhronizuju ca objekta: nit A
synchronized (o1) { . . // naredbe . synchronized (o2) { . . // naredbe . } }

nit B
synchronized (o2) { . . // naredbe . synchronized (o1) { . . // naredbe . } }

Pretpostavimo da se kod prvih sinhronizovanih naredbi niti A i B tokom izvravanja programa desio slu caj da je nit A zauzela katanac objekta o1 i da je odmah iza toga nit B zauzela katanac objekta o2. Kada se tokom daljeg izvravanja u nitima dode e do drugih sinhronizovanih naredbi, niti A i B c da c ekaju jedna drugu da oslobode katanac koji jedna nit treba i koji druga zauzima. Drugim re cima, nijedna nit ne moe da nastavi sa izvravanjem i obe su blokirane zbog mrtve petlje. Da ne bi dolo do mrtve petlja niti koje se sinhronizuju po vie objekata, u programu se moraju koristiti posebne tehnike. Na primer, spre cavanje pojave mrtve petlje niti moe se lako posti ci jednostavnim uredenjem resursa. Ovaj pristup sastoji se u tome da se najpre svi sinhronizuju ci objekti urede po nekom redosledu i zatim da se obezbedi da sve niti zauzimaju katance ovih objekata tim redosledom. U prethodnom primeru pretpostavimo da je, recimo, redosled sinhronizuju cih objekata o1 i o2. Onda se nit B mora prvo sinhronizovati po objektu o1, pa zatim po objektu o2. Kada nit A zauzme katanac objekta o1, nit B c e morati da c eka na katanac objekta o1 i ne ce mo ci da zauzme katanac objekta o2 koji dalje treba niti A. Zato c e nit A dalje mo ci da zauzme katanac objekta o2 i da zavri rad, kada c e nit B nastaviti sa svojim radom. Time je dakle spre cena pojava mrtve petlje ove dve niti.

9.3. Sinhronizacija niti

279

Nit za izvravanje gra ckih dogadaja


Svaki program u Javi se sastoji od bar jedne niti u kojoj se izvrava metod main() glavne klase. U gra ckom programu se automatski dodatno pokre ce posebna nit u kojoj se izvrava svako rukovanje GUI dogadajima i svako cr tanje komponenti na ekranu. Konstrukcija posebne niti je neophodna kako bi se obezbedilo da svaki rukovalac dogadajima zavri svoj rad pre slede ceg rukovaoca i da dogadaji cka ne prekidaju proces crtanja na ekranu. Ova gra nit se naziva nit za izvravanje dogadaja (engl. event dispatcher thread). Da bi se kod gra ckih programa predupredila mogu cnost mrtve petlje niti u nekim slu cajevima, potrebno je izvriti dodatne naredbe u niti za izvravanje gra ckih dogadaja. U tu svrhu se koriste stati cki metodi invokeLater() i invokeAndWait() iz klase SwingUtilities u paketu javax.swing. Naredbe koje treba izvriti u niti za izvravanje gra ckih dogadaja moraju se pisati unutar metoda run() jednog objekta tipa Runnable, a ovaj objekat se navodi kao argument metoda invokeLater() i invokeAndWait(). Razlika izmedu metoda invokeLater() i invokeAndWait() je u tome to se metod invokeLater() odmah zavrava nakon poziva metoda run(), bez c ekanja da se on zavri u niti za izvravanje gra ckih dogadaja. S druge strane, metod invokeAndWait() se ne zavrava odmah nego tek nakon to se potpuno zavri izvravanje metoda run() u niti za izvravanje gra ckih dogadaja. Do sada su gra cki programi u Javi bili pisani tako to su se u metodu main() napre konstruisali glavni okviri programa, a zatim su se ovi okviri prikazivali na ekranu tako to su se u cinili vidljivim. Ovo je u redu za ve cinu programa, ali u nekim slu cajevima to moe dovesti do problema mrtve petlje. Zbog toga se, da bi gra cki program bio apsolutno ispravan, taj postupak mora izvriti u niti za izvravanje gra ckih dogadaja na slede ci na cin:
public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { . . // Naredbe za konstruisanje okvira i njegovo podeavanje . } }); }

Obratite ovde panju na to da je argument metoda invokeLater() jedan objekat tipa Runnable koji pripada anonimnoj klasi. U narednom primeru je prikazan jednostavan gra cki program u kome se glavni okvir pokre ce iz niti za izvravanje gra ckih dogadaja.

280

9. P ROGRAMSKE NITI

import javax.swing.*; public class Grafi ckiProgram { public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { JFrame frame = new JFrame("Grafi cki program"); frame.add(new JLabel("Test niti za izvravanje doga daja")); frame.setLocationRelativeTo(null); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(250, 200); frame.setVisible(true); } }); } }

Primer: prikaz procenta zavretka zadatka


Ukoliko se u gra ckom programu neki postupak dugo izvrava, korisno je na neki na cin prikazati koliki deo tog postupka je zavren kako bi korisnik imao povratnu informaciju o tome kada c e se postupak otprilike zavriti. Komponenta JProgressBar moe posluiti u takve svrhe, jer gra cki prikazuje neku vrednost iz ograni cenog intervala. Ovaj interval izmedu neke minimalne i maksimalne vrednosti predstavljen je horizontalnom pravougaonom trakom koja se popunjava sleva na desno. (Traka moe biti prikazana i vertikalno kada se popunjava odozdo na gore.) Izgled ove trake je ilustrovan na slici 9.8.
minimum vrednost maksimum

Slika 9.8: Traka za prikaz procenta zavretka zadatka.

Klasa JProgressBar sadri vie metoda kojima se moe manipulisati trakom za prikaz procenta zavretka zadatka. Nepotpun spisak ovih metoda je: JProgressBar() Konstruktor kojim se konstruie horizontalna traka sa minimalnom vredno cu 0 i maksimalnom vrednos cu 100.

9.3. Sinhronizacija niti

281

void setOrientation(int p) Menja se poloaj trake u horizontalni ili vertikalni. void setValue(int v) Menja se aktuelna vrednost trake. void setString(String s) Menja se aktuelni tekst koji se prikazuje unutar trake. void setStringPainted(boolean b) Menja se indikator c ija vrednost ukazuje da li se tekst unutar trake prikazuje ili ne. Traka za prikaz procenta zavretka zadatka implementira se c esto u posebnoj niti radi pokazivanja stanja u kome se nalazi izvravanje drugih niti. Ovaj pristup je ilustrovan u narednom gra ckom programu u kome se kopira izabrana datoteka u drugu datoteku, uz istovremeni prikaz procenta zavrenog kopiranja (slika 9.9).

Slika 9.9: Kopiranje datoteke i procenat zavrenog kopiranja. U glavnoj klasi KopiranjeDatoteke gra ckog programa koji je prikazan u nastavku, formira se samo osnovni korisni cki interfejs, bez obra canja mnogo panje na ostale detalje otkrivanja greaka i izvetavanja korisnika. U glavnoj klasi je denisana unutranja klasa ZadatakKopiranja koja implementira interfejs Runnable radi kopiranja date datoteke. Kada se pritisne dugme za po cetak kopiranja, konstruie se i startuje posebna nit u kojoj se izvodi zadatak kopiranja date datoteke. Kako se originalna datoteka kopira u drugu datoteku, povremeno se aurira traka za prikaz procenta zavretka kopiranja.
import java.awt.*; import java.awt.event.*; import javax.swing.*;

282

9. P ROGRAMSKE NITI

import javax.swing.border.*; import java.io.*; public class KopiranjeDatoteke extends JFrame { private private private private JProgressBar traka = new JProgressBar(); JButton dugme = new JButton("Kopiraj"); JTextField original = new JTextField(); JTextField kopija = new JTextField();

// Konstruktor public KopiranjeDatoteke() { JPanel panel1 = new JPanel(); panel1.setLayout(new BorderLayout()); panel1.setBorder(new TitledBorder("Originalna datoteka")); panel1.add(original, BorderLayout.CENTER); JPanel panel2 = new JPanel(); panel2.setLayout(new BorderLayout()); panel2.setBorder(new TitledBorder("Kopija datoteke")); panel2.add(kopija, BorderLayout.CENTER); JPanel panel3 = new JPanel(); panel3.setLayout(new GridLayout(2, 1)); panel3.add(panel1); panel3.add(panel2); JPanel panel4 = new JPanel(); panel4.add(dugme); this.add(traka, BorderLayout.NORTH); this.add(panel3, BorderLayout.CENTER); this.add(panel4, BorderLayout.SOUTH); traka.setStringPainted(true); // prikazati tekst unutar trake dugme.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { // Konstruisanje i startovanje niti za kopiranje datoteke new Thread(new ZadatakKopiranja()).start(); } }); } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { JFrame okvir = new KopiranjeDatoteke();

9.3. Sinhronizacija niti

283

okvir.setLocationRelativeTo(null); okvir.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); okvir.setTitle("Kopiranje datoteke"); okvir.setSize(300, 200); okvir.setVisible(true); } }); } // Zadatak kopiranja datoteke i auriranja procenta zavrenog kopiranja class ZadatakKopiranja implements Runnable { private int p; // procenat zavrenog kopiranja public void run() { BufferedInputStream ulaz = null; BufferedOutputStream izlaz = null; try { // Konstruisanje ulaznog toka File imeUlaza = new File(original.getText().trim()); ulaz = new BufferedInputStream( new FileInputStream(imeUlaza)); // Konstruisanje izlaznog toka File imeIzlaza = new File(kopija.getText()); izlaz = new BufferedOutputStream( new FileOutputStream(imeIzlaza)); long n = ulaz.available(); // veli cina ulazne datoteke // ... u bajtovima traka.setValue(0); // po cetna vrednost trake long k = 0; // aktuelan broj u citanih bajtova datoteke byte[] bafer = new byte[256]; // prostor za privremeno // ... u citane bajtove int b; // broj privremeno u citanih bajtova while ((b = ulaz.read(bafer, 0, bafer.length)) != -1) { izlaz.write(bafer, 0, b); k = k + b; p = (int)(k * 100 / n); traka.setValue(p); // auriranje procenta kopiranja } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally {

284

9. P ROGRAMSKE NITI

try { if (ulaz != null) ulaz.close(); if (izlaz != null) izlaz.close(); } catch (Exception e) { } } } } }

Obratite panju na to da je izvodenje kopiranja datoteke u posebnoj niti od sutinske vanosti za ispravnost programa. U suprotnom slu caju, traka za prikaz procenta zavrenog kopiranja ne bi se aurirala sve dok se ceo postupak kopiranja ne zavri! Naime, izvravanje metoda actionPerformed() odvija se u istoj niti za izvravanje gra ckih dogadaja kao i operacija crtanja na ekranu trake za prikaz procenta zavrenog zadatka. To zna ci da crtanje trake u celom vremenskom intervalu od po cetka do zavretka kopiranja datoteke ne bi moglo da se aurira na ekranu (mada bi se vrednost trake povremeno aurirala). Ali, kopiranjem datoteke u posebnoj niti obezbedeno je paralelno crtanje trake za prikaz procenta zavrenog kopiranja.

9.4 Kooperacija niti


Niti mogu medusobno saradivati i na druge na cine osim deljenjem re sursa. Na primer, jedna nit moe proizvoditi neki rezultat koji koristi druga nit. To onda uslovljava odredena ograni cenja na poredak po kome se niti mo raju paralelno izvravati. Ako druga nit dode cke u kojoj joj je potreban do ta rezultat prve niti, druga nit moda mora da stane i sa ceka, jer prva nit jo nije proizvela potreban rezultat. Poto druga nit ne moe da nastavi, ona prelazi u neaktivno, blokirano stanje. Ali onda mora postojati i neki na cin da druga nit bude obavetena kada je rezultat prve niti spreman, kako bi se druga nit aktivirala i nastavila svoje izvravanje. U Javi se ova vrsta c ekanja i obevetavanja moe posti ci metodima wait() i notify() koji su denisani kao objektni metodi u klasi Object. Opti princip je da kada se u jednoj niti poziva metod wait() za neki objekat, ta nit prelazi u blokirano stanje dok se ne pozove metod notify() za isti objekat. Metod notify() mora se o cigledno pozvati u drugoj niti, jer se prva nit ne izvrava u blokiranom stanju.

9.4. Kooperacija niti

285

Tipi cni postupak je dakle da se u prvoj niti A pozove metod wait() kada je potreban rezultat druge niti B koji jo nije spreman. S druge strane, kada se u niti B proizvede potreban rezultat, onda se poziva metod notify() da bi se nit A aktivirala i iskoristila raspoloiv rezultat. Pozivanje metoda notify() kada nit A ne c eka nije greka, ve c prosto takav poziv nema nikakvog efekta. Drugim re cima, ukoliko je obj neki objekat, u niti A se izvrava programski fragment koji ima otprilike ovaj oblik:
if (!rezultatSpreman()) // rezultat nije spreman obj.wait(); // sa cekati dok rezultat ne bude spreman koristiRezultat(); // koristi rezultat poto je spreman

dok se istovremeno u niti B izvrava ovaj programski fragment:


proizvediRezultat(); obj.notify(); // poslati obavetenje da je rezultat proizveden

Paljivi c itaoci su moda primetili da ovo reenje nije potpuno ispravno, jer moe dovesti do mrtve petlje. Naime, niti A i B se mogu izvravati po slede cem redu: 1. U niti A se izvrava if naredba i nalazi se da rezultat nije spreman; 2. Pre nego to se u niti A izvri poziv obj.wait(), u niti B se proizvede potreban rezultat i izvri se poziv obj.notify(); ekanja na obavetenje da je 3. U niti A se izvrava poziv obj.wait() radi c rezultat spreman. Sada u tre cem koraku nit A c e c ekati na obavetenje koje nikad ne ce do ci, ci na cin izvravanja jer se u niti B ve c izvrio poziv obj.notify(). Ovaj mogu niti A i B dovodi dakle do beskona cnog c ekanja niti A da nastavi rad. O cigledno je da se cela if naredba u niti A mora izvriti bez prekidanja i zato je neophodno sinhronizovati niti A i B. Drugime re cima, programske fragmente obe niti treba pisati unutar sihronizovanih naredbi. Za sinhronizuju ci objekat prirodno je koristiti isti objekat obj koji se koristi za pozivanje metoda wait() i notify(). U stvari, u Javi je ovo prirodno reenje obavezno: u nekoj niti se mogu pozivati metodi obj.wait() i obj.notify() samo za vreme kada je katanac objekta obj u posedu te niti. Ukoliko to nije slu caj, izbacuje se izuzetak tipa IllegalMonitorStateException. Rukovanje ovim izuzetkom nije obavezno i u programu se taj izuzetak obi cno ne hvata. Ali metod wait() moe izbaciti proveravani izuzetak tipa InterruptedException, pa se poziv tog metoda mora nalaziti unutar naredbe try u kojoj se hvata ovaj izuzetak. Prethodni primer niti A i B je zapravo uvodni deo opteg problema proizvoda c u kome jedna nit proizvodi neki rezultat koji se troi u c/potroa

286

9. P ROGRAMSKE NITI

drugoj niti. Pretpostavimo da se potroa cu prenosi rezultat od proizvoda ca preko deljive promenljive rezultat koja po cetno ima vrednost null. Nakon to proizvede potreban rezultat, proizvoda c ga upisuje u tu promenljivu. Potroa c moe proveriti da li je rezultat spreman proveravanjem te promenljive da li ima vrednost null. Ako se koristi objekat katanac za sinhronizaciju, onda proizvoda c ima ovaj oblik:
r1 = proizvediRezultat(); // nije sinhronizovano! synchronized (katanac) { rezultat = r1; katanac.notify(); }

Sa druge strane, potroa c ima ovaj oblik:


synchronized (katanac) { while (rezultat == null) { try { katanac.wait(); } catch (InterruptedException e) { } } r2 = rezultat; } koristiRezultat(r2); // nije sinhronizovano!

Procesi proizvodnje i kori cenja rezultata (metodi proizvediRezultat() i koristiRezultat()) nisu sinhronizovani kako bi se mogli paralelno izvravati s drugim nitima koje su moda sinhronizovane po istom objektu katanac. Poto je rezultat deljiva promenljiva, njeno kori cenje mora biti sinhronizovano. Zato se svako pozivanje na promenljivu rezultat mora nalaziti unutar sinhronizovanih naredbi. Cilj je da se obezbedi to ve ca paralelnost rada proizvoda ca, odnosno da se to manje programskog koda (ali ne ca i potroa preterano manje) nalazi u sinhronizovanim naredbama. U prethodnom primeru, vrlo paljivi c itaoci su moda primetili potencijalni problem sinhronizacije niti proizvoda ca po istom objektu ca i potroa katanac. Naime, ukoliko potroa c zauzme katanac i izvri katanac.wait(), potroa c je blokiran dok proizvoda c ne izvri poziv katanac.notify(). Ali da bi proizvoda c c izvrio ovaj poziv, proizvoda c mora zauzeti katanac koji ve dri potroa c. Da li je ovo klasi can primer mrtve petlje dve niti? Odgovor je negativan, jer je katanac.wait() specijalan slu caj mehanizma sinhronizacije: odmah nakon izavravanja katanac.wait() u niti potroa ca, ova nit automatski oslobada ceg objekta katanac. To daje priliku katanac sinhronizuju niti proizvoda ca da zauzme katanac i da se izvri poziv katanac.notify()

9.4. Kooperacija niti

287

koji se nalazi unutar sinhronizovanog bloka. Nakon izvravanja ovog sinhronizovanog bloka u niti proizvoda ca niti potroa ca kako bi se ca, katanac se vra ova nit mogla nastaviti. Reenje jednostavnog problema proizvoda c, u kome se proiz c/potroa vodi i koristi samo jedan rezultat, moe se vrlo lako generalizovati. U optem slu caju, jedan ili vie proizvoda ca proizvodi vie rezultata koji se koriste od strane jednog ili vie potroa ca. U tom slu caju se umesto samo jednog deljenog rezultata vodi evidenicja o svim rezultatima koji su proizvedeni a nisu jo iskori ceni. Ti rezulati mogu biti organizovani u obliku liste (dinami ckog niza) rezultata, pri c emu niti proizvoda ca dodaju proizvedene rezultate na kraj ove liste i niti potroa ca uklanjaju rezultate sa po cetka ove liste radi kori cenja. Jedino vreme kada je neka nit prirodno blokirana i mora due da c eka jeste kada nit potroa ca pokuava da uzme jedan rezulat iz liste, a lista rezultata je prazna. Pod pretpostavkom da TipRezultata predstavlja tip proizvedenih i kori cenih rezultata, opti model proizvoda c moe se lako obuhvatiti c/potroa jednom klasom na slede ci na cin:
/** da cPotroa c predstavlja listu rezultata * Objekat tipa Proizvo * koji se mogu koristiti. Rezultati se u listu dodaju pozivom * metoda dodaj, a iz liste se uklanjaju pozivom metoda ukloni. * Ako je lista prazna kada se pozove metod ukloni, taj metod * se ne zavrava dok neki rezultat ne bude raspoloiv. */ public class Proizvo da cPotroa c { // Lista proizvedenih rezultata koji nisu jo iskori ceni private ArrayList<TipRezultata> rezultati = new ArrayList<TipRezultata>(); public void dodaj(TipRezultata r) { synchronized (rezultati) { rezultati.add(r); // dodati rezultat u listu rezultati.notify(); // obavestiti nit koja ceka u metodu ukloni } } public TipRezultata ukloni() { TipRezultata r; synchronized (rezultati) { // Ako je lista prazna, cekati na obavetenje iz metoda dodaj

288

9. P ROGRAMSKE NITI

while (rezultati.size() == 0) { try { rezultati.wait(); } catch (InterruptedException e) { } } // U ovoj ta cki je raspoloiv bar jedan rezultat r = rezultati.remove(0); } return r; } }

Na kraju spomenimo jo dve c injenice o metodima wait() i notify(). U programu se moe desiti da je nekoliko niti blokirano c ekaju ci na obavetenje da mogu nastaviti s radom. Poziv obj.notify() c e aktivirati samo jednu od niti koje c ekaju na obj. Metod notifyAll() slui za obavetavanje svih niti: pozivom obj.notifyAll() aktivira ce se sve niti koje c ekaju na obj. Metod wait() se moe pozvati i sa jednim parametrom koji predstavlja broj milisekundi. Nit u kojoj se izvrava obj.wait(ms) c eka ce na obavetenje najvie navedeni broj milisekundi. Ako obavetenje ne stigne u tom intervalu, nit c e se nakon isteka intervala aktivirati i nastaviti izvravanje. U praksi se ova mogu cnost naj ce ce koristi da bi se nit koja c eka aktivirala s vremena na vreme kako bi se mogao uraditi neki periodi cni zadatak, na primer da bi se prikazala poruka da nit i dalje c eka na zavretak odredene operacije.

G LAVA 10

M RENO PROGRAMIRANJE

Da bi ra cunari mogli da medusobno alju i primaju podatke, oni moraju biti zi cki povezani u mreu. Broj ra cunara i njihova prostorna rasprostranjenost u mrei obi cno odreduje to da li se mrea ra cunara smatra lokalnom, regionalnom ili globalnom (Internet). Fizi cka povezanost ra cunara ostvaruje se raznim sredstvima: i canim kablovima, opti ckim kablovima, satelitskim vezama, bei cnim vezama i tako dalje. Ali pored hardverske povezanosti, za komunikaciju ra cunara u mrei je potrebna i njihova softverska povezanost, odnosno potrebni su programi u njima koji zapravo medusobno razmenjuju podatke. U ovom poglavlju se opirnije govori o pisanju takvih programa u Javi.

10.1 Komunikacija ra cunara u mrei


Jedan program na nekom ra cunaru u mrei komunicira sa drugim programom na drugom ra cunaru na vrlo sli can na cin na koji se obavlja ulaz i izlaz programa preko datoteka. Mrea se, pojednostavljeno re ceno, smatra samo jo jednim mogu cim izvorom i prijemnikom podataka. Naravno, rad sa mreom nije tako jednostavan kao rad sa datotekama na disku, ali u Javi se za mreno programiranje koriste ulazni i izlazni tokovi podataka, ba kao to se to radi u optem slu caju programskog ulaza i izlaza. Ipak treba imati u vidu da uspostavljanje veze izmedu cunara zahteva dodatni napor u odnosu na dva ra prosto otvaranje datoteke, jer dva ra cunara na neki na cin moraju da se sloe oko otvaranja konekcije izmedu njih. Dodatno, slanje podataka izmedu dva ra cunara zahteva sinhronizaciju kako bi jedan ra cunar bio spreman da primi podatke koje drugi ra cunar alje. Komunikacija ra cunara u mrei odvija se na unapred precizno denisan na cin koji se naziva protokol . Dva osnovna protokola na kojima se zasniva komunikacija izmedu cunara nazivaju se Internet Protocol (IP ) i Transmis ra

290

10. M RENO PROGRAMIRANJE

sion Control Protocol (TCP ). Ovaj par protokola je medusobno vrlo povezan tako da se obi cno zajedni cki ozna cava skra cenicom TCP/IP. IP je protokol niskog nivoa za slanje i primanje podataka izmedu cunara u manjim dva ra delovima koji se nazivaju paketi. TCP je protokol vieg nivoa koji se oslanja na IP radi uspostavljanja logi cke veze izmedu cunara i obezbedivanja dva ra tokova podataka izmedu njih. Pored toga, TCP je protokol kojim se garantuje prenos svih paketa od jednog do drugog ra cunara i to onim redom kojim su poslati. U stvari, u nekim specijalnim primenama, umesto TCP protokola moe se koristiti drugi osnovni protokol koji se naziva User Datagram Protocol (UDP ). UDP je ekasniji protokol od TCP-ja, ali se njegovom primenom ne garantuje pouzdana dvosmerna komunikacija. U Javi se mogu koristiti oba protokola TCP i UDP za mreno programiranje, ali se u ovoj knjizi podrazumeva TCP protokol zbog njegove mnogo ire primene u praksi. Pored kori cenja protokola, radi komuniciranja u mrei neki ra cunar mora na neki na cin da nazna ci s kojim drugim ra cunarom od svih onih u mrei eli da komunicira. Zbog toga svaki ra cunar u mrei ima jedinstvenu IP adresu po kojoj se razlikuje od drugih ra cunara. IP adrese su 32-bitni celi brojevi koji se obi cno piu u obliku c etiri decimalna broja odvojena ta ckama, na primer 192.168.1.8. Svaki od ova c etiri broja predstavlja 8-bitni ceo broj iz intervala od 0 do 255 u grupi od 32 bita redom sleva na desno. Ta cnije, ovo su tradicionalne adrese koje se koriste u starijoj verziji 4 IP protokola, pa se ponekad radi preciznosti nazivaju IPv4 adrese. Najnovija verzija 6 IP protokola koristi 128-bitne cele brojeve za adrese koji se nazivaju IPv6 adrese. Kako nije lako raditi sa toliko mnogo brojeva kojima se ozna cavaju razlic iti ra cunari, mnogi ra cunari imaju i smislena imena koja se lake pamte, na primer java.sun.com ili www.singidunum.ac.rs. Ova domenska imena su takode jedinstvena, ali nisu zamena za IP adrese nego samo olakavaju rad sa ra cunarima u mrei. Kada jedan ra cunar alje zahtev drugom ra cunaru koriste ci njegovo domensko ime radi upostavljanja veze, ovo domensko ime drugog ra cunara mora se najpre prevesti u njegovu IP adresu i tek onda se moe poslati zahtev na odgovaraju cu IP adresu. Zadatak prevodenja domen skih imena u IP adrese obavljaju posebni ra cunari u mrei koji se nazivaju DNS serveri. Jedan ra cunar moe imati vie IP adresa, kako IPv4 tako i IPv6, kao i vie domenskih imena. Jedna od ovih IP adresa je standardno 127.0.0.1 koja se koristi kada jedan program treba da komunicira sa drugim programom na istom ra cunaru. Ova IP adresa se naziva adresa petlje (engl. loopback address) i za nju se obi cno moe koristiti domensko ime localhost . Standardni paket u Javi koji sadri klase za mreno programiranje naziva

10.2. Veb programiranje

291

se java.net. Klase u ovom paketu obezbeduju dva na cina mrenog progra miranja koji se grubo mogu nazvati veb programiranje i klijent-server programiranje. Prvi na cin je laki na cin koji se zasniva na veb protokolima viskog nivoa koji se nalaze iznad osnovnih TCP/IP protokola. Veb protokoli kao to su HTTP , FTP i drugi koriste TCP/IP protokole za neposredno slanje i primanje podataka, ali za veb programiranje nije potrebno poznavanje svih detalja njihovog rada. Na ovaj na cin se u programu mogu ostvariti sli cne mogu cnosti kao to su one koje imaju standardni veb pretraiva ci (Internet Explorer, Mozilla Firefox, . . . ). Glavne klase za ovaj stil mrenog programiranja nalaze se u paketima java.net.URL i java.net.URLConnection. Objekat tipa URL predstavlja apstrakciju adrese nekog HTML dokumenta ili drugog resursa na vebu. Ova adresa se tehni cki naziva univerzalni lokator resursa, a skra cenica URL poti ce od engleskog termina Universal Resource Locator za tu adresu. S druge strane, objekat tipa URLConnection predstavlja otvorenu mrenu konekciju sa nekim veb resursom. Druga vrsta mrenog programiranja u Javi je optija, ali zato i mnogo nieg nivoa. Ona se zasniva na TCP/IP protokolima i na modelu klijent-server za komunikaciju dva ra cunara u mrei. Ipak, radi pojednostavljenja ovog nac ina programiranja, mnogi tehni cki detalji TCP/IP protokola apstrahovani su konceptom soketa (engl. socket, u prevodu utika c). Program koristi soket za uspostavljanje konekcije sa drugim programom na drugom ra cunaru u mrei. Komunikacija dva ra cunara preko mree obuhvata zapravo dva soketa, po jedan na svakom ra cunaru koji medusobno komuniciraju. (Ovo je verovatno i razlog za termin utika c kojim se pokuava izraziti analogija sa zi ckim stavljanjem kabla u jedan utika c na ra cunaru radi povezivanja ra cunara u mreu.) Klasa java.net.Socket slui za predstavljanje soketa koji se koriste za mrenu komunikaciju u programu. Odmah treba naglasiti da soketi koji pripadaju klasi Socket jesu objekti koji i nemaju ba mnogo veze sa predstavom stvarnih utika ca. Naime, neki program moe imati nekoliko soketa u isto vreme i svaki od njih moe povezivati taj program sa drugim programom koji se izvrava na razli citom ra cunaru u mrei. Pri tome, sve ove konekcije se uspostavljaju preko istih zi ckih veza izmedu cunara u mrei. ra

10.2 Veb programiranje


Svaki resurs na vebu (dokument, datoteka, slika, . . . ) ima svoju adresu koja ga jedinstveno ozna cava. Na primer, osnovna stranica sajta za Javu ima adresu http://java.sun.com/index.html. Ova adresa se naziva univerzalni lokator resursa (engl. Universal Resource Locator ) ili u argonu kra ce url. Uni-

292

10. M RENO PROGRAMIRANJE

verzalni lokator resursa slui da bi se u veb pretraiva cu ta cno odredilo koji resurs se trai kako bi ga veb pretraiva c mogao na ci na vebu. Objekat koji pripada klasi URL predstavlja jedinstvenu url adresu resursa na vebu. Taj objekat slui da se metodima iz klase URLConnection uspostavi konekcija s datim resursom na odgovaraju coj adresi. Potpuna url adresa se obi cno navodi kao string, recimo "http://java.sun.com/index.html". Potpuna url adresa resursa se moe odrediti i relativno u odnosu na drugu url adresu koja se naziva osnova ili kontekst. Na primer, ako je osnova jednaka http://java.sun.com/, onda relativna url adresa u obliku index.html ukazuje na resurs c ija je puna adresa http://java.sun.com/index.html. Objekat klase URL nije obi can string, ve c se moe konstruisati na osnovu url adrese u obliku stringa. Objekat klase URL moe se konstruisati i na osnovu drugog objekta tipa URL, koji predstavlja osnovu, kao i stringa koji odreduje relativni url u odnosu na tu osnovu. Zaglavlja odogovaraju cih konstruktora klase URL su:
public URL(String url) throws MalformedURLException public URL(URL osnova, String relativni-url) throws MalformedURLException

Primetimo da oba konstruktora izbacuju poseban izuzetak ukoliko navedeni stringovi ne predstavljaju sintaksno ispravan url. Ovaj izuzetak je objekat klase MalformedURLException koja je naslednica klase IOException. To zna ci da se izuzetkom tipa MalformedURLException mora obavezno rukovati u programu. Drugim re cima, konstruktori klase URL moraju se nalaziti unutar naredbe try u kojoj se hvata ovaj izuzetak ili unutar metoda u c ijem zaglavlju se nalazi klauzula throws kojom se nagovetava mogu cnost izbacivanja tog izuzetka. Drugi konstruktor se naro cito koristi za pisanje apleta. U klasi JApplet su denisana dva metoda koja kao razultat daju dve korisne osnove za relativni url. Prvi metod je getDocumentBase() c iji je rezultat objekat tipa URL. Ovaj objekat predstavlja url adresu veb strane koja sadri aplet. Na taj na cin se u apletu mogu preuzeti dodatne datoteke koje se nalaze na istoj adresi. Pisanjem, na primer,
URL url = new URL(getDocumentBase(), "podaci.txt");

u apletu se dobija url koji ukazuje na datoteku podaci.txt koja se nalazi na istom ra cunaru i u istom direktorijumu kao i HTML dokument koji sadri aplet. Drugi, sli can metod u klasi JApplet je getCodeBase() c iji je rezultat objekat tipa URL koji predstavlja url adresu datoteke sa bajtkodom apleta. (Loka-

10.2. Veb programiranje

293

cije datoteka koje sadre opis veb strane na HTML jeziku i bajtkod apleta ne moraju biti iste.) U klasi URL je denisan metod openConnection() kojim se uspostavlja konekcija s resursom na datoj url adresi. Ovaj metod kao rezultat daje objekat tipa URLConnection koji predstavlja otvorenu konekciju s resursom. Taj objekat moe se dalje koristiti za formiranje ulaznog toka radi c itanja podataka iz resursa na odgovaraju coj url adresi. Formiranje ulaznog toka preko otvorene konekcije postie se metodom getInputStream(). Metodi openConnection() i getInputStream() mogu izbaciti proveravani izuzetak tipa IOException kojim se mora rukovati u programu. Na primer, u slede cem fragmentu se konstruie (binarni) ulazni tok za c itanje resursa koji se nalazi na adresi datoj stringom urlString:
URL url = new URL(urlString); URLConnection urlVeza = url.openConnection(); InputStream urlUlaz = urlVeza.getInputStream();

Dalji postupak c itanja podataka iz ulaznog toka odvija se na potpuno isti na cin kao to se to radi za datoteku na disku na primer, umotavanjem binarnog toka u tekstualni tok mogu se c itati tekstualni podaci iz resursa na vebu. Prilikom c itanja nekog resursa na vebu obi cno je korisno znati vrstu informacija koje sadri taj resurs. Za tu svrhu slui metod getContentType() u klasi URLConnection koji kao rezultat vra ca string koji opisuje sadraj resursa na vebu. Ovaj string ima specijalni oblik koji se naziva mime format , na primer: text/plain, text/html, image/jpeg, image/gif i tome sli cno. U optem slu caju, mime format sastoji se od dva dela. Prvi deo opisuje opti tip informacija koje sadri resurs, recimo da li je to tekst ili slika (text ili image). Drugi deo mime formata blie odreduje vrstu informacija unutar opte kategorije iz prvog dela, recimo da li je tekst obi can (plain) ili je slika u gif zapisu. Mime format je prvobitno uveden za opis sadraja poruka elektronske pote, ali se danas skoro univerzalno koristi za opis sadraja bilo koje datoteke ili drugog resursa na vebu. (Naziv mime za ovaj format poti ce od skra cenice engleskog termina Multipurpose Internet Mail Extensions.) Vra cena vrednost metoda getContentType() moe biti null ukoliko se ne moe odrediti vrsta informacija koje sadri resurs na vebu ili to nije jo poznato u trenutku poziva metoda. Ovaj drugi slu caj se moe desiti pre formiranja ulaznog toka, pa da se metod getContentType() obi cno poziva posle metoda getInputStream(). Radi boljeg razumevanja prethodnih pojmova, u nastavku je prikazan metod za c itanje tekstualne datoteke na datoj url adresi. U tom metodu se otvara

294

10. M RENO PROGRAMIRANJE

konekcija s datim resursom, proverava se da li ovaj sadri tekstualnu vrstu informacija i zatim se prikazuje njegov sadraj na ekranu. Mnoge naredbe u ovom metodu mogu izbaciti izuzetke, ali se njima rukuje na jednostavan na cin tako to metod u zaglavlju sadri klauzulu throws IOException. Time se glavnom programu preputa odluka o tome ta konkretno treba uraditi u slu caju neke greke.
public void prikaiTekstURL(String urlString) throws IOException { // Otvoranje konekcije sa resursom na url adresi // i formiranje ulaznog toka preko te konekcije URL url = new URL(urlString); URLConnection urlVeza = url.openConnection(); InputStream urlUlaz = urlVeza.getInputStream(); // Proveravanje da li je sadraj resursa neka forma teksta String mime = urlVeza.getContentType(); if ((mime == null) || (mime.startsWith("text") == false)) throw new IOException("Url ne ukazuje na tekstualnu datoteku"); // Kopiranje redova teksta iz ulaznog toka na ekran dok se // ne nai de na kraj datoteke (ili se ne desi neka greka) BufferedReader ulaz; ulaz = new BufferedReader(new InputStreamReader(urlUlaz)); while (true) { String jedanRed = ulaz.readLine(); if (jedanRed == null) break; System.out.println(jedanRed); } }

Obratite panju na to da kada se ovaj metod koristi za prikazivanje neke datoteke na vebu, mora se navesti pun oblik njene url adrese koji po cinje preksom http://.

Primer: jednostavan veb pretraiva c


Pored prikazivanja obi cnog teksta datoteke na datoj url adresi, u Javi se na jednostavan na cin moe i interpretirati sadraj neke datoteke napisan u HTML ili RTF formatu i prikazati prevedena slika tog sadraja. Naime, u Swing biblioteci se nalazi klasa JEditorPane koja predstavlja gra cku komponentu za automatsko prikazivanje kako datoteka sa obi cnim tekstom, tako i datoteka sa tekstom u HTML ili RTF formatu. Upotrebom ove komponente ne moraju se pisati eksplicitne naredbe za c itanje datoteke na datoj url adresi, nego se

10.2. Veb programiranje

295

prikazivanje njenog (interpretiranog) sadraja postie jednostavnim pozivom metoda:


public void setPage(URL url) throws IOException

Komponenta tipa JEditorPane predstavlja zapravo prozor pravog editora teksta. Medutim, zbog relativno skromnih mogu cnosti za uredivanje teksta, ova komponenta se skoro uvek koristi samo za prikazivanje teksta (tj. njeno svojstvo Editable obi cno dobija vrednost false). Komponenta tipa JEditorPane proizvodi dogadaj tipa HyperlinkEvent kada se u njenom prozoru klikne na neki hiperlink. Objekat dogadaja ovog tipa sadri url adresu izabranog hiperlinka, koja se onda moe koristiti u rukovaocu ovog dogadaja za metod setPage() radi prikazivanja odgovaraju ce veb stranice. Ove mogu cnosti su iskori cene za pisanje jednostavnog programa koji poseduje najosnovnije funkcije pravog veb pretraiva ca (slika 10.1). Ukoliko se unese url adresa HTML datoteke u tekstualnom polju glavnog prozora programa i pritisne taster Enter, sadraj te datoteke se prikazuje u okviru editora tipa JEditorPane. Pored toga, ukoliko se klikne na neki hiperlink unutar prikazanog sadraja, prikazuje se novi sadraj odgovaraju ceg resursa.

Slika 10.1: Jednostavan veb pretraiva c.

import java.io.*; import java.net.*;

296

10. M RENO PROGRAMIRANJE

import import import import

java.awt.*; java.awt.event.*; javax.swing.*; javax.swing.event.*;

public class VebPretraiva c extends JFrame { private JLabel urlOznaka; // oznaka url polja private JTextField urlPolje; // url polje private JEditorPane urlStrana; // sadraj veb strane public VebPretraiva c(String naslov) { super(naslov); urlOznaka = new JLabel("URL: "); urlPolje = new JTextField(); // Dodavanje rukovaoca doga daja pritiska na Enter urlPolje.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { try { URL url = new URL(urlPolje.getText().trim()); urlStrana.setPage(url); // prikazivanje HTML datoteke } catch (IOException ex) { System.out.println(ex); } } }); urlStrana = new JEditorPane(); urlStrana.setEditable(false); // Dodavanje rukovaoca doga daja pritiska na hiperlink urlStrana.addHyperlinkListener(new HyperlinkListener() { public void hyperlinkUpdate(HyperlinkEvent e) { if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) try { urlStrana.setPage(e.getURL()); // prikazivanje linka } catch (IOException ex) { System.out.println(ex); } } }); JPanel trakaURL = new JPanel(); trakaURL.setLayout(new BorderLayout(3,3)); trakaURL.add(urlOznaka, BorderLayout.WEST); trakaURL.add(urlPolje, BorderLayout.CENTER);

10.3. Klijent-server programiranje

297

JPanel sadraj = new JPanel(); sadraj.setLayout(new BorderLayout(3,3)); sadraj.add(trakaURL, BorderLayout.NORTH); sadraj.add(new JScrollPane(urlStrana), BorderLayout.CENTER); setContentPane(sadraj); setBounds(50,100,800,600); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String[] args) { VebPretraiva c okvirJVP = new VebPretraiva c("Jednostavni veb pretraiva c"); okvirJVP.setVisible(true); } }

10.3 Klijent-server programiranje


Da bi dva programa mogli da komuniciraju preko mree, oba programa moraju da obrazuju po jedan soket koji predstavlja logi cku krajnju ta cku konekcije izmedu programa. Nakon to se konstruiu soketi i uspostavi mrena konekcija izmedu cni njih, u programima se soketi dalje mogu koristiti kao obi izvori i prijemnici podataka na standardni na cin programskog ulaza/izlaza. Drugim re cima, za dvosmernu komunikaciju dva programa na razli citim rac unarima, svaki program koristi ulazni i izlazni tok podataka preko uspostavljene konekcije izmedu soketa. Podaci koje jedan program upisuje u svoj izlazni tok alju se drugom ra cunaru. Tamo se oni usmeravaju u ulazni tok programa na drugom kraju mrene konekcije. Zato kada ovaj drugi program c ita podatke iz svog ulaznog toka, on zapravo dobija podatke koji su primljeni preko mree od prvog programa. Citanje i pisanje podataka preko otvorene mrene konekcije nije dakle nita druga cije od ulazno-izlaznih operacija za datoteke na disku. Najtei deo mrenog programiranja je, u stvari, uspostavljanje same konekcije izmedu dva programa. Taj postupak obihvata dva soketa i sledi unapred odreden niz koraka. Jedan program mora najpre konstruisati soket koji pasivno c eka dok ne stigne zahtev za uspostavljanje konekcije od nekog drugog soketa. Na suprotnoj strani potencijalne konekcije, drugi program konstruie aktivni soket koji alje zahtev za konekcijom pasivnom soketu koji c eka. Kada pasivni soket primi zahtev, on aktivnom soketu odgovara potvrdno na zahtev za ko-

298

10. M RENO PROGRAMIRANJE

nekcijom i time je konekcija uspostavljena.1 Nakon toga, oba programa na logi ckim krajevima konekcije mogu formirati ulazni i izlazni tok za primanje i slanje podataka preko otvorene konekcije. Komunikacija izmedu dva programa preko ovih tokova podataka se dalje odvija sve dok jedan od programa ne zatvori konekciju. Program koji konstruie soket koji c eka na zahtev za uspostavljanje konekcije naziva se server , a ovaj pasivni soket koji se u njemu koristi naziva se serverski soket . Drugi program koji se povezuje sa serverom naziva se klijent , a njegov aktivni soket kojim se alje zahtev za uspostavljanje konekcije sa serverom naziva se klijentski soket . Osnovna ideja ovog modela klijent-server programiranja je da se server nalazi negde u mrei i da c eka na zahtev za uspostavljanje konekcije od nekog klijenta. Za server se pretpostavlja da nudi neku vrstu usluge koja je potrebna klijentima, a klijenti je mogu dobiti nakon uspostavljanja konekcije sa serverom. U praksi je c esto potrebno da server moe raditi s vie klijenata u isto vreme. Naime, kada jedan klijent uspostavi konekciju sa serverom, serverski soket ne obustavlja c ekanje na nove zahteve za uspostavljanje konekcije od drugih klijenata. Zato dok se jedan klijent usluuje (ili se to radi za vie njih), drugi klijenti mogu uspostaviti konekciju sa serverom i istovremeno biti uslueni od strane servera. Kao to c itaoci verovatno pretpostavljaju, za obezbedivanje ovog paralelizma u radu servera koriste se programske niti o kojima smo govorili u poglavlju 9. Mada se to ne vidi zbog visokog nivoa veb programiranja, u klasi URL iz prethodnog odeljka koristi se zapravo klijentski soket za mrenu komunikaciju. Na drugoj strani konekcije je serverski program koji prihvata zahteve za konekcijom od objekta tipa URL, zatim c ita zahtev za speci cnom datotekom na serverskom ra cunaru i, na kraju, odgovara tako to alje sadraj te datoteke klijentskom objektu tipa URL. Nakon prenosa svih podataka u datoteci, server zatvara konekciju. Uspostavljanje konekcije izmedu serveskog i klijentskog programa je dodatno iskomplikovano mogu cno cu da na jednom ra cunaru moe raditi nekoliko programa koji istovremeno komuniciraju sa drugim programima preko mree ili time to jedan program moe komunicirati sa vie programa preko mree. Zbog toga se logi cki krajevi mrene konekcije na ozna cavaju samo IP adresama odgovaraju cih ra cunara nego i takozvanim brojem porta. Broj porta je obi can 16-bitni ceo broj iz intervala od 0 do 65536, ali brojevi od 0 do 1024 su rezervisani za standardne veb servise. Prema tome, serverski program ne c eka prosto na zahtev za uspostavljanje konekcije od klijenata
1 Pasivni soket moe i odbiti zahtev aktivnog soketa za uspostavljanje konekcije.

10.3. Klijent-server programiranje

299

on c eka na te zahteve na speci cnom portu. Da bi uspostavio konekciju sa serverom, potencijalni klijent mora zato znati kako IP adresu (ili domensko ime) ra cunara na kome server radi, tako i broj porta na kome server o cekuje zahteve za uspostavljanje konekcije. Na primer, veb server obi cno o cekuje zahteve na portu 80, dok emejl server to radi na portu 25; drugim standardnim veb servisima su takode dodeljeni standardni brojevi portova.

Serverski i klijentski soketi


U Javi se za uspostavljanje TCP/IP konekcije koriste klase ServerSocket i
Socket iz paketa java.net. Objekat klase ServerSocket predstavlja serverski

soket koji c eka na zahteve za uspostavljanje konekcije od klijenata. Objekat klase Socket predstavlja jednu krajnju ta cku uspostavljene mrene konekcije. Objekat ovog tipa moe biti klijentski soket koji serveru alje zahtev za uspostavljanje konekcije. Ali objekat tipa Socket moe biti i soket koji server konstruie radi uspostavljanja konekcije sa klijentom, odnosno radi obezbedivanja druge logi cke ta cke jedne takve konekcije. Na taj na cin server moe konstruisati vie soketa koji odgovaraju viestrukim konekcijama sa razli citim klijentima. Objekat tipa ServerSocket ne u cestvuje neposredno u komunikaciji izmedu c samo c eka na zahteve za uspostavljanje servera i klijenata, ve konekcije od klijenata i konstruie soket tipa Socket radi uspostavljanja veze izmedu cavanja njihove dalje komunikacije. servera i klijenta i omogu Kod pisanje serverskog programa mora se najpre konstruisati serverski eka zahteve za soket tipa ServerSocket i navesti broj porta na koji server c uspostavljanje konekcije. Na primer:
ServerSocket server = new ServerSocket(port);

Port u argumentu konstruktora klase ServerSocket mora biti broj tipa


int iz intervala od 0 do 65536, ali generalno to treba biti broj ve ci od 1024.

Konstruktor klase ServerSocket izbacuje izuzetak tipa IOException ukoliko se serverski soket ne moe konstruisati i povezati sa navedenim portom. To obi cno zna ci da se navedeni port ve c koristi od strane nekog drugog servera. cinje Serverski soket tipa ServerSocket odmah nakon konstruisanja po da c eka na zahteve za uspostavljanje konekcije. Ali da bi se mogao prihvatiti i odgovoriti na jedan takav tahtev, mora se koristiti metod accept() iz klase ServerSocket. Kada stigne zahtev za uspostavljanje konekcije, metodom accept() se taj zahtev prihvata, uspostavlja se konekcija sa klijentom i kao rezultat se vra ca soket tipa Socket koji se dalje moe koristiti za komunikaciju sa klijentom. Na primer, odmah nakon konstruisanja serverskog

300

10. M RENO PROGRAMIRANJE

soketa server, odgovaranje na zahteve klijenata od strane servera postie se naredbom:


Socket konekcija = server.accept();

Kada se pozove metod accept(), njegovo izvravanje se blokira dok se ne primi zahtev za uspostavljanje konekcije (ili dok se ne desi neka greka kada se izbacuje izuzetak tipa IOException). Obratite panju na to da je onda blokiran i metod u kojem je pozvan metod accept(), odnosno da se u niti u kojoj se izvravaju ti metodi ne moe nita dalje izvravati. (Naravno, druge niti istog programa se normalno dalje izvravaju.) Metod accept() se moe pozivati vie puta za isti serverski soket radi prihvatanja zahteva od vie klijenata. Serverski soket nastavlja da c eka zahteve za uspostavljanje konekcije sve dok se ne zatvori pozivom metoda close(), ili dok se ne desi neka greka u programu. Nakon uspostavljanja konekcije c iji su krajevi soketi tipa Socket, pozivom metoda getInputStream() i getOutputStream() iz klase Socket se za njih mogu formirati ulazni i izlazni tokovi radi prenosa podataka preko uspostavljene konekcije. Metod getInputStream() kao rezulta vra ca objekat tipa InputStream, dok metod getOutputStream() kao rezulta vra ca objekat tipa OutputStream. Uzimaju ci u obzir sve do sada re ceno i pretpostavljaju ci da server treba da o cekuje zahteve na portu 2345, osnovna struktura serverskog programa bi otprilike imala slede ci oblik:
int port = 2345; try { ServerSocket server = new ServerSocket(port); while (true) { Socket konekcija = server.accept(); InputStream in = konekcija.getInputStream(); OutputStream out = konekcija.getOutputStream(); . . // Koristiti ulazni i izlazni tok, in i out, . // za komunikaciju sa klijentom . konekcija.close(); } } catch (IOException e) { System.out.println("Kraj rada servera usled greke: " + e); }

Na strani klijenta, soket koji predstavlja klijentski kraj konekcije konstruie se pozivom konstruktora klase Socket:

10.3. Klijent-server programiranje

301

public Socket(String ra cunar , int port) throws IOException

Prvi parametar ovog konstruktora je IP adresa ili domensko ime ra cunara na kome se izvrava server s kojim se eli uspostaviti konekcija. Drugi parametar je broj porta na kojem taj server o cekuje zahteve za uspostavljanje konekcije od klijenata. Konstruktor klase Socket ne zavrava svoj rad sve dok se konekcija ne uspostavi ili dok se ne desi neka greka. Pretpostavljaju ci da klijent uspostavlja konekciju sa serverom na adresi 192.168.1.8 i portu 2345, osnovna struktura klijentskog programa bi otprilike imala slede ci oblik:
String komp = "192.168.1.8"; int port = 2345; try { Socket konekcija = new Socket(komp,port); InputStream in = konekcija.getInputStream(); OutputStream out = konekcija.getOutputStream(); . . // Koristiti ulazni i izlazni tok, in i out, . // za komunikaciju sa serverom . konekcija.close(); } catch (IOException e) { System.out.println("Neuspeno uspostavljanje konekcije: " + e); }

Opti na cin komunikacije prethodno opisanih prototipova servera i klijenta ilustrovan je na slici 10.2.

Serverski ra cunar
Server
ServerSocket server = new ServerSocket(port); Socket konekcija = server.accept();

Klijentski ra cunar

Klijent U/I tok


Socket konekcija = new Socket(komp,port);

Slika 10.2: Model rada servera i klijenta. Napomenimo da je u prethodnim prototipovima za serverski i klijentski

302

10. M RENO PROGRAMIRANJE

program prikazano samo najosnovnije rukovanje grekama. U stvari, najtei deo mrenog programiranja je pisanje robustnih programa, jer je prenos podataka preko mree prili cno nepouzdan. Zbog toga je vrlo verovatna pojava raznih vrsta greaka u programu, to komplikuje postupak njihovog rukovanja radi uspenog oporavka programa ukoliko se desi neka greka.

Primer: jednostavni server i klijent


U ovom primeru su prikazani rudimentarni serverski i klijentski programi da bi se ilustrovala dvosmerna komunikacija izmedu njih. Osnovni model ovakve razmene podataka jeste da klijent alje podatke serveru, a server prima te podatke, koristi ih za neko izra cunavanje i dobijeni rezultat alje nazad klijentu. U ovom primeru, klijent alje pre cnik jednog kruga, a server izra cunava povrinu tog kruga i alje je klijentu. Prikaz rada servera i klijenta na ovaj na cin ilustrovan je na slici 10.3.

Slika 10.3: Dvosmerna komunikacija servera i klijenta. Server koristi uobi cajeni postupak za uspostavljanje konekcije i komunikaciju sa klijentom. Nakon konstruisanja serverskog soketa na portu 2345, server prihvata i uspostavlja konekciju sa klijentom. Kada se uspostavi konekcija sa klijentom, preko te konekcije se zatim konstruiu ulazni i izlazni tok tipa DataInputStream i DataOutputStream radi lakeg primanja i slanja realnih vrednosti tipa double. Na kraju se izvodi komunikacija sa klijentom tako to se ponavljaju tri koraka: c itanje pre cnika kruga iz ulaznog toka, izrac unavanje povrine kruga i upisivanje dobijenog rezultata u izlazni tok.
import import import import java.io.*; java.net.*; java.awt.*; javax.swing.*;

public class Server {

10.3. Klijent-server programiranje

303

public static void main(String[] args) { ServerOkvir okvir = new ServerOkvir(); okvir.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); okvir.setVisible(true); okvir.startServer(); } } class ServerOkvir extends JFrame { private JTextArea transkript; // prikaz rada servera // Konstruktor public ServerOkvir() { setTitle("Server"); setSize(500, 300); transkript = new JTextArea(); transkript.setEditable(false); // Dodavanje tekstualnog prikaza u okvir servera setLayout(new BorderLayout()); add(new JScrollPane(transkript), BorderLayout.CENTER); } // Glavni metod servera za komunikaciju sa klijentom public void startServer() { transkript.append("Server je startovan ... " + \n); try { // Konstruisanje serverskog soketa ServerSocket srvSoket = new ServerSocket(2345); // Cekanje na zahtev za konekcijom od klijenta Socket kliSoket = srvSoket.accept(); srvSoket.close(); // serverski soket nije vie potreban // Konstruisanje ulaznog i izlaznog toka za razmenu podataka DataInputStream odKlijenta = new DataInputStream( kliSoket.getInputStream()); DataOutputStream kaKlijentu = new DataOutputStream( kliSoket.getOutputStream()); while (true) { // Primanje pre cnika od klijenta double r = odKlijenta.readDouble();

304

10. M RENO PROGRAMIRANJE

transkript.append("Pre cnik kruga primljen od klijenta: "); transkript.append(r + "\n"); // Izra cunavanje povrine kruga double p = r * r * Math.PI; // Slanje povrine kruga klijentu kaKlijentu.writeDouble(p); kaKlijentu.flush(); transkript.append("Povrina kruga poslata klijentu: "); transkript.append(p + "\n"); } } catch(IOException e) { transkript.append("Prekid konekcije sa klijentom: " + e + "\n"); } } }

Serverski program se mora pokrenuti pre nego to se pokrene klijentski program. Poto se server i klijent izvravaju na istom ra cunaru, klijent uspostavlja konekciju sa ra cunarom localhost na portu 2345. Nakon toga se preko te konekcije konstruiu ulazni i izlazni tokovi radi razmene podataka sa serverom. U klijentu se pre cnik kruga koji se alje serveru unosi u tekstualno polje. Pritisak tastera Enter u tom polju generie akcijski dogadaj iji rukova c lac zapravo obavlja slanje i primanje podataka. Na taj na cin se serveru mogu slati vie pre cnika i od servera redom primati povrine odgovaraju cih krugova.
import import import import import java.io.*; java.net.*; java.awt.*; java.awt.event.*; javax.swing.*;

public class Klijent { public static void main(String[] args) { KlijentOkvir okvir = new KlijentOkvir(); okvir.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); okvir.setVisible(true); } } class KlijentOkvir extends JFrame { private JTextField pre cnikPolje; // polje za unos pre cnika private JTextArea transkript; // prikaz rada klijenta

10.3. Klijent-server programiranje

305

// Ulazno/izlazni tokovi private DataInputStream odServera; private DataOutputStream kaServeru; // Konstruktor public KlijentOkvir() { setTitle("Klijent"); setSize(500,300); pre cnikPolje = new JTextField(); pre cnikPolje.setHorizontalAlignment(JTextField.LEFT); // Pridruivanje rukovaoca pritiska na taster Enter u polju pre cnikPolje.addActionListener(new RukovalacEntera()); transkript = new JTextArea(); transkript.setEditable(false); // Dodavanje oznake i tekstualnog polja u panel JPanel panel = new JPanel(); panel.setLayout(new BorderLayout()); panel.add(new JLabel("Pre cnik kruga: "),BorderLayout.WEST); panel.add(pre cnikPolje,BorderLayout.CENTER); // Dodavanje panela i tekstualnog prikaza u okvir klijenta setLayout(new BorderLayout()); add(panel,BorderLayout.NORTH); add(new JScrollPane(transkript),BorderLayout.CENTER);

try { // Konstruisanje klijentskog soketa za konekciju // sa serverom "localhost" na portu 2345 Socket kliSoket = new Socket("localhost",2345); // Socket kliSoket = new Socket("192.168.1.8",2345); transkript.append( "Uspostavljena konekcija sa serverom ... " + "\n"); // Konstruisanje ulaznog toka za primanje podataka od servera odServera = new DataInputStream( kliSoket.getInputStream()); // Konstruisanje izlaznog toka za slanje podataka serveru kaServeru = new DataOutputStream( kliSoket.getOutputStream()); } catch (IOException e) { transkript.append(

306

10. M RENO PROGRAMIRANJE

"Neuspeno uspostavljanje konekcije sa serverom: " + e); } } // Unutranja klasa rukovaoca doga daja private class RukovalacEntera implements ActionListener { public void actionPerformed(ActionEvent ae) { try { // Preuzimanje pre cnika kruga iz tekstualnog polja double r = Double.parseDouble(pre cnikPolje.getText().trim()); // Slanje pre cnika kruga serveru kaServeru.writeDouble(r); kaServeru.flush(); transkript.append("Pre cnik kruga poslat serveru: "); transkript.append(r + "\n"); // Primanje povrine kruga od servera double p = odServera.readDouble(); transkript.append("Povrina kruga primljena od servera: "); transkript.append(p + "\n"); } catch (IOException e) { transkript.append("Greka u komunikaciji: " + e); } } } }

Obratite panju na to da kada se u serveru povrina kruga alje klijentu pozivom kaKlijentu.writeDouble(p), ili kada se u klijentu pre cnik kruga alje serveru pozivom kaServeru.writeDouble(r), onda se iza toga za odgovaraju ci izlazni tok poziva metod flush(). Metod flush() se nalazi u klasi svake vrste izlaznog toka i osigurava da se podaci upisani na jednom kraju izlaznog toka odmah poalju drugom kraju izlaznog toka. Ina ce, zavisno od implementacije Jave, podaci ne moraju biti poslati odmah preko mree, nego se mogu sakupljati sve dok se ne skupi dovoljna koli cina podataka za slanje. Ovakav pristup se primenjuje radi optimizacije mrenih operacija, ali se time moe izazvati neprihvatljivo kanjenje u prenosu podataka, pa c ak i to da neki podaci ne budu preneseni kada se konekcija zatvori. Zbog ovih razloga metod flush() treba skoro uvek pozivati kada se koristi izlazni tok za slanje podataka preko mrene konekcije. U suprotnom, program moe raditi razli cito na razli ctim platformama ra cunara.

10.4. Vienitno mreno programiranje

307

10.4 Vienitno mreno programiranje


Priroda mrenih program c esto zahteva da se vie zadataka obavlja u isto vreme. Jedan primer je slu caj vie klijenata koji mogu uspostaviti konekciju s jednim serverom radi dobijanja nekih rezultata od njega. U ovom slu caju server mora istovremeno komunicirati sa vie klijenata. Drugi tipi cni primer je asinhrona komunikacija dva programa preko mree kada redosled primanja i slanja podataka ne mora biti naizmeni can. U ovom slu caju programi moraju zadatke primanja i slanja podataka obavljati paralelno, jer je vreme stizanja i slanja podataka medusobno nezavisno. U ovim slu cajevima je dakle neophodno kori cenje vie niti za paralelno izvravanje zadataka. U prvom primeru, istovremena komunikacija jednog servera i vie klijenata odvija se u posebnim nitima. U drugom primeru, programi na oba kraja konekcije izvravaju primanje i slanje podataka u posebnim nitima kako ne bi bili blokirani c ekaju ci da podaci stignu kada druga strana nije spremna da ih poalje. U ovom odeljku se redom govori o ova dva na cina primene vie programskih niti izvravanja kod mrenih programa.

Vienitni server za vie klijenata


Obi cno vie klijenata istovremeno uspostavlja konekciju s jednim serverom radi dobijanja nekih rezultata od njega. Na primer, klijenti u formi veb pretraiva ca mogu u isto vreme iz c itavog Interneta uspostaviti konekciju s jednim veb serverom radi prikazivanja odredenog veb sajta. Istovremena ko munikacija servera i vie klijenata u Javi moe se ostvariti ukoliko se koristi posebna nit za svaku konekciju. Opti postupak za ovaj pristup ima slede ci oblik:
while (true) { Socket konekcija = serverskiSoket.accept(); Thread nitKlijenta = new Thread(new KlasaZadatka(konekcija)); nitKlijenta.start(); }

U svakoj iteraciji while petlje uspostavlja se nova konekcija na zahtev od klijenta i kao rezultat se dobija jedan klijentski soket na serverskoj strani konekcije. Zatim se ovaj klijentski soket koristi za konstruisanje nove niti u kojoj se odvija komunikacija izmedu cin u isto servera i novog klijenta. Na taj na vreme moe postojati vie konekcija koje su ilustrovane na slici 10.4.

308

10. M RENO PROGRAMIRANJE

Server
serverski soket
klijent. soket

...

klijent. soket

klijent. soket

Klijent 1

...

klijent. soket

Klijent n

Slika 10.4: Server koji istovremeno usluuje vie klijenata.

Primer: jednostavan vienitni server


Da bismo pokazali osnovni na cin rada vienitnog servera koji usluuje vie klijenata, u ovom primeru proirujemo server koji smo pokazali na strani 302 za izra cunavanje povrine kruga. Nakon uspostavljanja konekcije sa svakim novim klijentom, proireni server pokre ce posebnu nit za komunikaciju s njim. U toj niti se od jednog klijenta neprekidno prima pre cnik kruga i kao odgovor mu se alje povrina kruga. Poto je protokol komunikacije izmedu servera i klijenta nepromenjen, klijentski program je isti kao u primeru na strani 302. Ilustracija rada ovog vienitnog servera i dva klijenta prikazana je na slici 10.5.

Slika 10.5: Vienitni server i dva klijenta.

10.4. Vienitno mreno programiranje

309

import import import import

java.io.*; java.net.*; java.awt.*; javax.swing.*;

public class VienitniServer { public static void main(String[] args) { VienitniServerOkvir okvir = new VienitniServerOkvir(); okvir.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); okvir.setVisible(true); okvir.startServer(); } } class VienitniServerOkvir extends JFrame { private JTextArea transkript; // prikaz rada servera // Konstruktor public VienitniServerOkvir() { setTitle("Vienitni server"); setSize(500, 300); transkript = new JTextArea(); transkript.setEditable(false); // Dodavanje tekstualnog prikaza u okvir servera setLayout(new BorderLayout()); add(new JScrollPane(transkript), BorderLayout.CENTER); } // Glavni metod servera za uspostavljanje konekcije sa klijentima public void startServer() { transkript.append("Server je startovan ... " + \n); int k = 0; // broj klijenta try { // Konstruisanje serverskog soketa ServerSocket srvSoket = new ServerSocket(2345); while (true) { // Cekanje na zahtev za konekcijom od klijenta Socket kliSoket = srvSoket.accept(); k++; // novi klijent je uspostavio konekciju

310

10. M RENO PROGRAMIRANJE

// Nalaenje domenskog imena i IP adrese klijenta InetAddress inetAdresa = kliSoket.getInetAddress(); transkript.append("Domensko ime klijenta " + k + ": " + inetAdresa.getHostName() + "\n"); transkript.append("IP adresa klijenta " + k + ": " + inetAdresa.getHostAddress() + "\n"); // Konstruisanje niti za komunikaciju sa klijentom Thread nitKlijenta = new Thread( new RukovalacKlijenta(kliSoket,k)); // Startovanje niti za komunikaciju sa klijentom nitKlijenta.start(); transkript.append("Pokrenuta nit za klijenta " + k + "\n"); } } catch(IOException e) { transkript.append("Prekid rada servera: " + e + "\n"); } } // Unutranja klasa za zadatak koji se obavlja // u posebnoj niti (komunikacija sa klijentom) private class RukovalacKlijenta implements Runnable { private Socket kliSoket; private int klijent; // Konstruktor public RukovalacKlijenta(Socket kliSoket, int klijent) { this.kliSoket = kliSoket; this.klijent = klijent; } // Izvravanje niti public void run() { try { // Konstruisanje ulaznog i izlaznog toka za razmenu podataka DataInputStream odKlijenta = new DataInputStream( kliSoket.getInputStream()); DataOutputStream kaKlijentu = new DataOutputStream( kliSoket.getOutputStream()); while (true) { // Primanje pre cnika od klijenta double r = odKlijenta.readDouble(); transkript.append("Pre cnik kruga primljen od klijenta "); transkript.append(klijent + ": " + r + "\n");

10.4. Vienitno mreno programiranje

311

// Izra cunavanje povrine kruga double p = r * r * Math.PI; // Slanje povrine kruga klijentu kaKlijentu.writeDouble(p); kaKlijentu.flush(); transkript.append("Povrina kruga poslata klijentu "); transkript.append(klijent + ": " + p + "\n"); } } catch(IOException e) { transkript.append("Prekid konekcije sa klijentom "); transkript.append(klijent + ": " + e + "\n"); } } } }

Obratite panju u ovom programu na prikazivanje IP adrese i domenskog imena klijenta nakon uspostavljanja konekcije za serverom. U tu svrhu se koristi klasa InetAddress kojom se u Javi predstavlja internet adresa ra cunara. Internet adresa ra cunara obuhvata njegovu IP adresu (kako IPv4 tako i IPv6) i njegovo domensko ime. Da bi se ove informacije dobile o klijentu, koristi se objektni metod getInetAddress() iz klase Socket. Naime, kada se pozove za neki klijentski soket, ovaj metod kao rezultat daje objekat tipa InetAddress koji predstavlja internet adresu ra cunara na drugom kraju konekcije tog klijentskog soketa. Zato se u programu naredbom
InetAddress inetAdresa = kliSoket.getInetAddress();

dobija internet adresa klijentskog ra cunara, a njeni pojedini delovi (IP adresa i domensko ime) izdvajaju se metodima getHostAddress() i getHostName() iz klase InetAddress.

Asinhrona komunikacija programa


U prethodnim odeljcima su pokazani primeri u kojima nije zapravo istaknuta jedna od osnovnih karakteristika mrenih programa asinhrona priroda njihove medusobne komunikacija. Iz perspektive programa na jednom kraju mrene konekcije, poruke od programa na drugom kraju mogu sti ci u nepredvidljivim trenucima nad kojim program koji prima poruke nema uticaja. U nekim slu cajevima, kao na primer kod slanja i primanja pre cnika i povrine kruga, mogu ce je ustanoviti protokol po kome se komunikacija odvija korak po korak na sinhroni na cin od po cetka do kraja. Glavni nedostatak

312

10. M RENO PROGRAMIRANJE

sinhrone komunikacije jeste to to kada po protokolu program treba da primi podatke, on mora da c eka na njih i ne moe nita drugo da radi dok o cekivani podaci ne stignu. U Javi se asinhrona priroda mrene komunikacije moe realizovati upotrebom vie programskih niti. Ako se podsetimo iz poglavlja 9, jedna nit izvravanja u programu sastoji se od zasebnog niza naredbi koje se nezavisno i paralelno izvravaju s drugim programskim nitima. Zbog toga kada mreni program koristi vie paralelnih niti, neke niti mogu biti blokirane zbog c ekanja da stignu podaci, ali druge niti mogu nastaviti sa izvravanjem i obavljati bilo koji drugi korisni zadatak.

Primer: program za cetovanje


Dva korisnika mogu sinhrono razmenjivati poruke preko mree ( cetovati) tako to jedan korisnik poalje svoju poruku i zatim c eka da dobije odgovor od drugog korisnika. Asinhroni na cin razmene poruka je mnogo bolji, jer onda jedan korisnik moe slati poruke ne c ekaju ci da dobije odgovor od druge strane. Po takvom protokolu onda, poruke koje asinhrono stiu od drugog korisnika prikazuju se c im stignu. Gra cki program za asinhronu razmenu poruka zasniva se na posebnoj programskoj niti c iji je zadatak da c ita poruke koje stiu sa drugog kraja mre ne konekcije. Cim se pro cita jedna poruka u toj niti, ta poruka se prikazuje na ekranu i zatim se opet c eka da stigne slede ca poruka. Dok je nit za c itanje poruka blokirana c ekaju ci da stigne neka poruka, druge niti u programu mogu paralelno nastaviti sa izvravanjem. Speci cno, nit za reagovanje na gra cke dogadaje izazvane aktivnostima korisnika nije blokirana i u njoj se mogu slati poruke c im ih korisnik napie. Prikaz rada dva programa na razli citim ra cunarima za asinhronu razmenu poruka ilustrovan je na slici 10.6.

Slika 10.6: Asinhrono cetovanje preko mree. Program za cetovanje prikazan u nastavku moe igrati ulogu servera ili klijenta zavisno od toga da li je korisnik pritisnuo dugme za c ekanje na zahtev

10.4. Vienitno mreno programiranje

313

za uspostavljanje konekcije od udaljenog ra cunara ili je pak pritisnuo dugme za uspostavljanje konekcije sa udaljenim ra cunarom. Nakon uspostavljanja mrene konekcije dva programa, svaki korisnik moe drugom slati poruke koje se unose u predvideno polje na dnu prozora. Pritisak na taster Enter ili na dugme za slanje poruke proizvodi dogadaj ckoj niti kojim se rukuje u gra tako to se poruka alje korisniku na drugom kraju konekcije. Dok se poruke alju u gra ckoj niti, poruke se primaju u posebnoj niti koja se pokre ce c im se pritisne na odgovaraju ce dugme u prozoru za c ekanje ili uspostavljanje konekcije. U ovoj drugoj niti se obavlja i po cetno uspostavljanje konekcije da bi se izbeglo blokiranje gra ckog korisni ckog interfejsa ukoliko uspostavljanje konekcije potraje due vreme. U programu se koristi unutranja klasa RukovalacKonekcije za realizaciju posebne niti u kojoj se upravlja konekcijom preko koje se primaju poruke. Ova klasa sadri i nekoliko metoda koji se pozivaju u gra ckoj niti i koji se zato izvravaju u toj drugoj niti. Najzna cajniji medu njima je metod za slanje poruka koji se poziva i izvrava u gra ckoj niti kao reakcija na aktivnost korisnika. Svi ovi metodi su sinhronizovani kako ne bi dolo do greke trke dve niti usled promene stanja konekcije u sredini odgovaraju ce operacije.
import import import import import java.awt.*; java.awt.event.*; javax.swing.*; java.io.*; java.net.*;

public class ChatProgram extends JFrame { // Nabrojivi tipovi za tip programa i stanje konekcije private enum TipPrograma {SERVER,KLIJENT}; private enum StanjeKonekcije {POVEZANO,PREKINUTO}; // Podrazumevani parametri programa private TipPrograma program = null; private static String brojPorta = "2345"; private static String udaljeniRa cunar = "localhost"; // Nit uspostavljene konekcije private RukovalacKonekcije konekcija; // Dugmad i tekstualna polja u prozoru programa private JButton serverDugme, klijentDugme, prekiniDugme, poaljiDugme; private JTextField serverPortPolje; private JTextField udaljeniRa cunarPolje, udaljeniPortPolje; private JTextField porukaPolje; private JTextArea transkript;

314

10. M RENO PROGRAMIRANJE

// Konstruktor kojim se konstruie gravni prozor, // ali se prozor ne cini vidljivim zbog druge niti public ChatProgram(String naslov) { super(naslov); ActionListener rukovalac = new RukovalacDugmadima(); serverDugme = new JButton(" Cekaj vezu:"); serverDugme.addActionListener(rukovalac); klijentDugme = new JButton("Uspostavi vezu:"); klijentDugme.addActionListener(rukovalac); prekiniDugme = new JButton("Prekini vezu"); prekiniDugme.addActionListener(rukovalac); prekiniDugme.setEnabled(false); poaljiDugme = new JButton("Poalji poruku"); poaljiDugme.addActionListener(rukovalac); poaljiDugme.setEnabled(false); porukaPolje = new JTextField(); porukaPolje.addActionListener(rukovalac); porukaPolje.setEditable(false); transkript = new JTextArea(20,60); transkript.setLineWrap(true); transkript.setWrapStyleWord(true); transkript.setEditable(false); serverPortPolje = new JTextField(brojPorta,5); udaljeniPortPolje = new JTextField(brojPorta,5); udaljeniRa cunarPolje = new JTextField(udaljeniRa cunar,18); JPanel sadraj = new JPanel(); sadraj.setLayout(new BorderLayout(3,3)); sadraj.setBackground(Color.GRAY); sadraj.setBorder(BorderFactory.createLineBorder(Color.GRAY, 3)); JPanel trakaVeza = new JPanel(); trakaVeza.setLayout(new FlowLayout(FlowLayout.CENTER,3,3)); trakaVeza.add(serverDugme); trakaVeza.add(new JLabel("port")); trakaVeza.add(serverPortPolje); trakaVeza.add(Box.createHorizontalStrut(12)); trakaVeza.add(klijentDugme); trakaVeza.add(udaljeniRa cunarPolje);

10.4. Vienitno mreno programiranje

315

trakaVeza.add(new JLabel("port")); trakaVeza.add(udaljeniPortPolje); trakaVeza.add(Box.createHorizontalStrut(12)); trakaVeza.add(prekiniDugme); JPanel trakaPoruka = new JPanel(); trakaPoruka.setLayout(new BorderLayout(3,3)); trakaPoruka.setBackground(Color.GRAY); trakaPoruka.add(new JLabel("Unesi poruku:"), BorderLayout.WEST); trakaPoruka.add(porukaPolje, BorderLayout.CENTER); trakaPoruka.add(poaljiDugme, BorderLayout.EAST); sadraj.add(trakaVeza, BorderLayout.NORTH); sadraj.add(new JScrollPane(transkript), BorderLayout.CENTER); sadraj.add(trakaPoruka, BorderLayout.SOUTH); setContentPane(sadraj); pack(); setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); addWindowListener(new WindowAdapter() { public void windowClosed(WindowEvent we) { if (konekcija != null && konekcija.stanje() == StanjeKonekcije.POVEZANO) { konekcija.zatvori(); } System.exit(0); } }); } // Glavni metod kojim se konstruie i prikazuje glavni prozor public static void main(String[] args) { ChatProgram okvir = new ChatProgram("Program za cetovanje"); okvir.setVisible(true); } // Dodavanje poruke u deo za prikaz rada programa private void prikai(String poruka) { transkript.append(poruka); transkript.setCaretPosition(transkript.getDocument().getLength()); } // Unutranja klasa rukovaoca doga daja svih dugmadi u prozoru private class RukovalacDugmadima implements ActionListener { public void actionPerformed(ActionEvent ae) { Object izvorDoga daja = ae.getSource();

316

10. M RENO PROGRAMIRANJE

if (izvorDoga daja == serverDugme) { if (konekcija == null || konekcija.stanje() == StanjeKonekcije.PREKINUTO) { String portString = serverPortPolje.getText(); int port; try { port = Integer.parseInt(portString); if (port < 1025 || port > 65535) throw new NumberFormatException(); } catch (NumberFormatException e) { JOptionPane.showMessageDialog(ChatProgram.this, portString + " nije ispravan broj porta."); return; } klijentDugme.setEnabled(false); serverDugme.setEnabled(false); program = TipPrograma.SERVER; konekcija = new RukovalacKonekcije(port); } } else if (izvorDoga daja == klijentDugme) { if (konekcija == null || konekcija.stanje() == StanjeKonekcije.PREKINUTO) { String portString = udaljeniPortPolje.getText().trim(); int port; try { port = Integer.parseInt(portString); if (port < 1025 || port > 65535) throw new NumberFormatException(); } catch (NumberFormatException e) { JOptionPane.showMessageDialog(ChatProgram.this, portString + " nije ispravan broj porta."); return; } klijentDugme.setEnabled(false); serverDugme.setEnabled(false); program = TipPrograma.KLIJENT; konekcija = new RukovalacKonekcije( udaljeniRa cunarPolje.getText().trim(),port); } } else if (izvorDoga daja == prekiniDugme) { if (konekcija != null) konekcija.zatvori(); } else if (izvorDoga daja == poaljiDugme || izvorDoga daja == porukaPolje) {

10.4. Vienitno mreno programiranje

317

if (konekcija != null && konekcija.stanje() == StanjeKonekcije.POVEZANO) { konekcija.poalji(porukaPolje.getText()); porukaPolje.selectAll(); porukaPolje.requestFocus(); } } } } // Unutranja klasa za nit u kojoj se upravlja // konekcijom preko koje se razmenjuju poruke private class RukovalacKonekcije extends Thread { private private private private private private private volatile StanjeKonekcije s; String udaljeniRa cunar; int port; ServerSocket srvSoket; Socket kliSoket; PrintWriter izlaz; // izlazni tok za konekciju BufferedReader ulaz; // ulazni tok za konekciju

// Konstruktor kada program radi kao server RukovalacKonekcije(int port) { this.port = port; prikai("\nCEKANJE VEZE NA PORTU " + port + "\n"); start(); } // Konstruktor kada program radi kao klijent RukovalacKonekcije(String udaljeniRa cunar, int port) { this.udaljeniRa cunar = udaljeniRa cunar; this.port = port; prikai("\nUSPOSTAVLJANJE VEZE SA " + udaljeniRa cunar); prikai(" NA PORTU " + port + "\n"); start(); } // Trenutno stanje konekcije (metod se poziva iz druge niti) synchronized StanjeKonekcije stanje() { return s; } // Slanje poruke preko uspostavljene konekcije // (metod se poziva iz druge niti) synchronized void poalji(String poruka) { if (s == StanjeKonekcije.POVEZANO) { prikai("POALJI: " + poruka + "\n"); izlaz.println(poruka);

318

10. M RENO PROGRAMIRANJE

izlaz.flush(); if (izlaz.checkError()) { prikai("\n\nGREKA PRILIKOM SLANJA PODATAKA!"); zatvori(); } } } // Zatvaranje konekcije iz nekog razloga // (metod se poziva iz druge niti) synchronized void zatvori() { s = StanjeKonekcije.PREKINUTO; serverDugme.setEnabled(true); klijentDugme.setEnabled(true); prekiniDugme.setEnabled(false); poaljiDugme.setEnabled(false); porukaPolje.setEditable(false); try { if (kliSoket != null) kliSoket.close(); else if (srvSoket != null) srvSoket.close(); } catch (IOException e) { } } // Glavni metod koji definie nit izvravanja public void run() { try { if (program == TipPrograma.SERVER) { srvSoket = new ServerSocket(port); kliSoket = srvSoket.accept(); srvSoket.close(); } else if (program == TipPrograma.KLIJENT) { kliSoket = new Socket(udaljeniRa cunar,port); } // Priprema konekcije za razmenu poruka srvSoket = null; ulaz = new BufferedReader( new InputStreamReader(kliSoket.getInputStream())); izlaz = new PrintWriter(kliSoket.getOutputStream()); s = StanjeKonekcije.POVEZANO; prikai("\nUSPOSTAVLJENA VEZA\n"); porukaPolje.setEditable(true); porukaPolje.setText("");

10.4. Vienitno mreno programiranje

319

porukaPolje.requestFocus(); poaljiDugme.setEnabled(true); prekiniDugme.setEnabled(true); // Citanje jednog reda poruke od druge // strane sve dok je veza uspostavljena while (s == StanjeKonekcije.POVEZANO) { String red = ulaz.readLine(); if (red == null) { prikai("\nVEZA PREKINUTA SA DRUGE STRANE\n"); s = StanjeKonekcije.PREKINUTO; } else prikai("\nPRIMLJENO: " + red + "\n"); } } catch (ConnectException e) { prikai("\n\n GREKA: " + e + "\n"); } catch (Exception e) { if (s == StanjeKonekcije.POVEZANO) prikai("\n\n GREKA: " + e + "\n"); } // Na kraju izvravanja "po cistiti sve za sobom" finally { zatvori(); prikai("\n*** VEZA PREKINUTA ***\n"); kliSoket = null; ulaz = null; izlaz = null; srvSoket = null; } } } }

G LAVA 11

G ENERI CKO PROGRAMIRANJE

Termin generi cko programiranje odnosi se na pisanje programskog koda kojim se obezbeduje uniformna obrada struktura podataka razli citih tipova. Pretpostavimo da se u programu koristi dinami cki niz koji se sastoji od elemenata odredenog tipa, recimo int. Ukoliko je potrebno da elementi drugog dinami ckog niza budu drugog tipa, recimo String, deo programa u kojem se obraduje jedan takav niz bio bi identi can onom za elemente niza tip int, osim zamene jednog tipa drugim. Pisanje u sutini istog programskog koda vie puta nije naravno odlika dobrog programiranja, jer pored gubitka vremena uvek postoji mogu cnost greaka kod prepisivanja. Elegantno reenje ovog problema u Javi je upotreba parametrizovanih tipova radi generalizovanja programskog koda. Naime, klase, interfejsi i metodi u Javi mogu biti denisani s parametrima koji predstavljaju (klasne) tipove podataka. U ovom poglavlju se najpre govori o kori cenju takvih generi ckih programskih celina koje su na raspolaganju programerima u standardnim bibliotekama, a na kraju c e biti vie re ci o tome kako se deniu nove generi cke klase, interfejsi i metodi.

11.1 Uvod
Poznato je da, na primer, dinami cki niz tipa ArrayList moe imati elemente bilo kog tipa. To je posledica c injenice to je u klasi ArrayList denisano da se svaki niz tipa ArrayList sastoji zapravo od elemenata tipa Object. Kako se podrazumeva da je svaka klasa u Javi izvedena od najoptije klase Object, na osnovu principa podtipa onda sledi da objekti bilo kog tipa mogu biti vrednosti elemenata niza tipa ArrayList. Ali u Javi se moe i suziti tip elemenata dinami ckog niza, recimo da bude String, ukoliko se konstruie dinami cki niz tipa ArrayList<String>. Ovo je mogu ce zato to je denicija klase ArrayList zapravo parametrizovana

322

11. G ENERI CKO PROGRAMIRANJE

jednim tipom podataka i ima oblik ArrayList<T>. Dodatak <T> u deniciji klase predstavlja formalni generi cki tip koji se zamenjuje stvarnim konkretnim tipom prilikom kori cenja klase. (Po konvenciji, formalni generi cki tip ozna cava se jednim velikim slovom.) Dobra strana primene generi ckog programiranja sastoji se u tome to se dobijaju optije programske celine i time se poboljava c itljivost programa u kome se koriste. Druga, moda vanija prednost generi ckog programiranja jeste to to se time omogu cava rano otkrivanje greaka i tako program c ini pouzdanijim. Na primer, ako se u programu koriste dva niza a i b c iji elementi treba da budu celi brojevi i ako je a niz tipa ArrayList i b niz tipa ArrayList<Integer>, onda se dodela vrednosti pogrenog tipa elementima ovih nizova moe otkriti u razli citim fazama razvoja programa. Tako, naredba
a[0] = "greka";

ne moe biti otkrivena kao pogrena u prvoj fazi prevodenja programa, jer elementi niza a tipa ArrayList mogu biti bilo kog tipa i prevodilac ne moe znati da vrednost elementa a[0] treba da bude celobrojnog tipa, a ne pogrenog tipa String. Ova vrsta greke dakle moe biti otkrivena tek u kasnijoj fazi izvravanja programa, ali tada takva greka moe izazvati i vrlo ozbiljne posledice. Sa druge strane, naredba
b[0] = "greka";

bi ce otkrivena kao pogrena u ranoj fazi prevodenja programa, jer prevodilac moe iskoristi c injenicu da elementi niza b tipa ArrayList<Integer> moraju biti tipa Integer. Klasa ArrayList je samo jedna od vie standardnih klasa koje se koriste za generi cko programiranje u Javi. Ove gotove generi cke klase i interfejsi nazivaju se jednim imenom Sistem kolekcija u Javi (engl. Java Collection Framework). Ovaj sistem je proao kroz nekoliko faza razvoja od po cetne verzije, a u Javi 5.0 su dodati parametrizovani tipovi kao to je ArrayList<T>. Ta cnije, u Javi 5.0 su parametrizovane sve klase i interfejsi koje c ine Sistem kolekcija, pa c ak i neke klase koje nisu deo tog sistema. Zbog toga je u Javi mogu ce konstruisati generi cke strukture podataka c iji se tipovi mogu proveriti tokom prevodenja programa, a ne tek u fazi izvravanja programa. Treba ipak napomenuti da se parametrizovani tipovi mogu koristiti i bez parametara tako da je dozvoljeno koristiti, recimo, i obi cnu klasu ArrayList. Ponekad treba imati na umu da je implementacija parametrizovanih tipova u Javi speci cna po tome to se za svaku parametrizovanu klasu dobija samo jedna prevedena klasa. Na primer, postoji samo jedna prevedena klasa ArrayList.class za parametrizovanu klasu ArrayList<T>. Tako, parametrizovani tipovi ArrayList<String> i ArrayList<Integer>, kao i obi can

11.2. Sistem kolekcija u Javi

323

tip ArrayList, koriste jednu istu prevedenu klasu. Naime, konkretni tipovi String i Integer, koji su argumenti parametrizovanog tipa, slue samo kao informacija prevodiocu da ograni ci tip objekata koji mogu biti c lanovi strukture podataka. Ti konkretni tipovi nemaju nikakvog efekta u fazi izvravanja programa i c ak nisu ni poznati u toku izvravanja programa. Ovo gubljenje informacije o tipu u fazi izvravanja programa kod parametrizovanih tipova postavlja izvesna ograni cenja u radu sa parametrizovanim tipovima. Na primer, logi cki uslov u slede coj if naredbi nije ispravan:
if (a instanceof ArrayList<String>) ...

Razlog za ovo je to to se operator instanceof primenjuje u fazi izvravanja programa, a tada postoji samo obi can tip ArrayList. Sli cno, ne moe se konstruisati obi can niz c iji je bazni tip ArrayList<String>, na primer:
new ArrayList<String>[100]

jer se i operator new primenjuje u fazi izvravanja programa, a tada ne postoji parametrizovani tip ArrayList<String>.

11.2 Sistem kolekcija u Javi


Generi cke strukture podataka u Javi mogu se podeliti u dve kategorije: kolekcije i mape. Kolekcija je struktura podataka koja se posmatra samo kao zajedni cka grupa nekih objekata, bez obra canja mnogo panje na mogu ce dodatne medusobne odnose izmedu objekata te grupa. Mapa je struktura podataka u kojoj se moe prepoznati preslikavanje objekata jednog skupa u objekte drugog skupa. Telefonski imenik je primer jedne takve strukture podataka, jer u njemu su imena pretplatnika pridruena telefonskim brojevima c ime je denisano preslikavanje skupa imena pretplatnika u skup telefonskih brojeva. Kolekcije i mape su u Javi predstavljene parametrizovanim interfejsima Collection<T> i Map<T,S>. Slova T i S ovde stoje umesto bilo kog tipa osim nekog od primitivnih tipova. Primetimo jo da parametrizovani tip Map<T,S> ima dva parametra tipa, T i S. (U optem slu caju, parametrizovani tip moe imati vie parametara tipa odvojenih zapetama.)

Kolekcije
Kolekcije objekata u Javi se prema speci cnim medusobnim odnosima njihovih objekata dalje dele u dve podvrste: liste i skupove. Lista je kolekcija u kojoj su objekti linearno uredeni, odnosno denisan je redosled objekata

324

11. G ENERI CKO PROGRAMIRANJE

kolekcije po kome je odreden prvi objekat, drugi objekat i tako dalje. Preciznije, osnovno svojstvo liste je da se iza svakog objekta u listi, osim poslednjeg, nalazi ta cno jedan drugi objekat liste. Osnovno svojstvo skupa kao kolekcije objekata je da ne postoje duplikati objekata u skupu. Naglasimo da se kod skupova u optem slu caju ne podrazumeva da medu lanovima skupa postoji c bilo kakav medusobni poredak. Liste i skupovi su predstavljeni parametrizovanim interfejsima List<T> i Set<T> koji nasleduju interfejs Collection<T>. Prema tome, svaki objekat koji implementira interfejs List<T> ili Set<T> automatski implementira i interfejs Collection<T>. Interfejs Collection<T> denie opte operacije koje se mogu primeniti na svaku kolekciju, dok interfejsi List<T> i Set<T> deniu dodatne operacije koje su speci cne samo za liste i skupove. Svaka aktuelna struktura podataka koja je kolekcija, lista ili skup mora naravno biti objekat konkretne klase koja implementira odgovaraju ci interfejs. Klasa ArrayList<T>, na primer, implementira interfejs List<T> i time takode ci da se svi metodi koji implementira interfejs Collection<T>. To zna se nalaze u interfejsu List<T>, odnosno Collection<T>, mogu koristiti sa objektima tipa, recimo, ArrayList<String>. Interfejs Collection<T>. Interfejs Collection<T> sadri metode za obavljanje osnovnih operacija nad bilo kojom kolekcijom objekata. Kako je kolekcija vrlo opti pojam strukture podataka, to su i operacije koje se mogu primeniti nad svim kolekcijama vrlo generalne. One su generalne u smislu da se mogu primeniti nad raznim vrstama kolekcija koje sadre razne tipove objekata. Ako pretpostavimo da je k objekat klase koja implementira interfejs Collection<T> za neki konkretan neprimitivni tip T, onda se za kolekciju k mogu primeniti slede ci metodi denisani u interfejsu Collection<T>: k.size() vra ca celobrojnu vrednost tipa int koja predstavlja veli cinu kolekcije k, odnosno aktuelni broj objekata koji se nalazi u kolekciji k. k.isEmpty() vra ca logi cku vrednost true ukoliko je kolekcija k prazna, odnosno ukoliko je njena veli cina jednaka 0. k.clear() uklanja sve objekte iz kolekcije k. k.add(o) dodaje objekat o kolekciji k. Tip objekta o mora biti T; ako to nije slu caj, dobija se sintaksna greka prilikom prevodenja. Metod add() vra ca logi cku vrednost koja ukazuje na to da li se dodavanjem datog objekta kao rezultat dobila modikovana kolekcija. Na primer, dodavanje duplikata nekog objekta u skup ne proizvodi nikakav efekat i zato se originalna kolekcija ne menja.

11.2. Sistem kolekcija u Javi

325

k.contains(o) vra ca logi cku vrednost true ukoliko kolekcija k sadri objekat o. Objekat o moe biti bilo kog tipa, jer ima smisla proveravati da li se objekat bilo kog tipa nalazi u kolekciji. k.remove(o) uklanja objekat o iz kolekcije k, ukoliko se dati objekat nalazi u kolekciji. Metod remove() vra ca logi cku vrednost koja ukazuje na to da li se dati objekat nalazio u kolekciji, odnosno da li se kao rezultat uklanjanja dobila modikovana kolekcija. Tip objekta o ne mora biti T. k.containsAll(kk) vra ca logi cku vrednost true ukoliko kolekcija k sadri sve objekte kolekcije kk. Kolekcija kk moe biti bilo kog tipa oblika Collection<T>. k.addAll(kk) dodaje sve objekte kolekcije kk u kolekciju k. Kolekcija kk moe biti bilo kog tipa oblika Collection<T>. k.removeAll(kk) uklanja sve objekte kolekcije kk iz kolekcije k. Kolekcija kk moe biti bilo kog tipa oblika Collection<T>. k.retainAll(kk) zadrava samo objekte kolekcije kk u kolekciji k, a sve ostale uklanja iz kolekcije k. Kolekcija kk moe biti bilo kog tipa oblika Collection<T>. k.toArray() vra ca niz tipa Object[] koji sadri sve objekte u kolekciji k. Obratite panju na to da je vra ceni niz tipa Object[], a ne T[], mada se njegov tip moe eksplicitnom konverzijom promeniti u drugi tip. Na primer, ako su svi objekti u kolekciji k tipa String, onda se eksplicitnom konverzijom (String[]) k.toArray() dobija niz stringova koji sadri sve stringove u kolekciji k. Poto se ovi metodi nalaze u interfejsu Collection<T>, oni moraju biti denisani u svakoj klasi koja implementira taj interfejs. To zna ci da funkcija ovih metoda zavisi od same implementacije u konkretnoj klasi i da prethodni opis njihove semantike ne mora biti ta can za svaku kolekciju. Ali, za sve standardne kolekcije u Javi, semantika metoda u interfejsu Collection<T> upravo je ona koja je gore navedena. Potencijalni problem koji proisti ce iz optosti interfejsa Collection<T> jeste i to to neki njegovi metodi ne moraju imati smisla za sve kolekcije. Velic ina nekih kolekcija, na primer, ne moe se promeniti nakon konstruisanja kolekcije, pa metodi za dodavanje i uklanjanje objekata u tim kolekcijama nemaju smisla. U takvim slu cajevima, iako je u programu dozvoljeno da se pozivaju ovi metodi, u fazi izvravanja programa oni izazivaju izuzetak tipa UnsupportedOperationException.

326

11. G ENERI CKO PROGRAMIRANJE

Jednakost i poredenje objekata kolekcije. U interfejsu Collection<T> ne koliko metoda zavisi od toga kako se odreduje jednakost objekata u kolekciji. Tako se u metodima k.contains(o) i k.remove(o), recimo, dobija odgovaraju ci rezultat prema tome da li se u kolekciji k pronalazi neki objekat koji je jednak datom objektu o. Ali, pod razumnom jednako cu objekata obi cno se ne podrazumeva ono to se dobija primenom operatora ==. Operatorom == proverava se da li su dva objekta jednaka u smislu da zauzimaju isti memorijski prostor. To je u optem slu caju sasvim razli cito od prirodne interpretacije jednakosti dva objekta u smislu da predstavljaju istu vrednost. Na primer, prirodno je smatrati da su dva stringa tipa String jednaka ukoliko sadre isti niz znakova, dok je pitanje da li se oni nalaze u istoj memorijskoj lokaciji potpuno nevano. U klasi Object, koja se nalazi na samom vrhu hijerarhije klasa u Javi, denisan je metod equals(o) koji vra ca ta cno ili neta cno prema tome da li je jedan objekat jednak drugom. Ovaj metod se koristi u mnogim standardnim klasama Sistema kolekcija u Javi radi utvrdivanja da li su dva objekta jednaka. U klasi Object, rezultat izraza o1.equals(o2) denisan je da bude ekvivalentan rezultatu izraza o1 == o2. Kao to smo napomenuli, ova denicija nije odgovaraju ca za mnoge druge prave klase koje automatski nasleduju klasu Object, pa metod equals() treba redenisati (nadja cati) u njima. Zbog toga je ovaj metod u klasi String denisan tako da je s.equals(o) ta cno ukoliko je objekat o tipa String i o sadri isti niz znakova kao string s. U svojim klasama programeri bi takode trebalo da deniu poseban metod equals() da bi se dobio o cekivan rezultat prilikom odredivanja jednakosti objekata tih klasa. U narednom primeru je u klasi PunoIme denisan takav metod kojim se korektno odreduje da li su puna imena dve osobe jednaka:
public class PunoIme { private String ime, prezime; public boolean equals(Object o) { try { PunoIme drugo = (PunoIme)o; // konverzija argumenta u PunoIme return ime.equals(drugo.ime) && prezime.equals(drugo.prezime); } catch (Exception e) { return false; // ako je o jednako null ili nije tipa PunoIme } } . . // Ostali metodi

11.2. Sistem kolekcija u Javi

327

. }

Sa ovako denisanim metodom equals(), klasa PunoIme se moe potpuno bezbedno koristiti u kolekcijama. U suprotnom slu caju, metodi u interfejsu Collection<PunoIme> kao to su contains() i remove() ne bi dali ta can rezultat. Pored jednakosti objekata, druga relacija medu proizvoljnim objektima za koju ne postoji o cigledna interpretacija jeste njihov poredak. Ovaj problem pojavljuje se naro cito kod sortiranja objekata u kolekciji po rastu cem ili opadaju cem redosledu. Da bi se objekti mogli medusobno uporedivati, njihova klasa mora da implementira interfejs Comparable. U stvari, ovaj interfejs je denisan kao parametrizovan interfejs Comparable<T> kojim se omogu cuje poredenje sa objektima tipa T. U interfejsu Comparable<T> denisan je samo jedan metod:
public int compareTo(T o)

Rezultat poziva o1.compareTo(o2) treba da bude negativan broj ukoliko se objekat o1 nalazi pre objekta o2 u relativnom poretku. Taj rezultat treba da bude pozitivan broj ukoliko se objekat o1 nalazi iza objekta o2 u relativnom poretku. Najzad, rezultat treba da bude nula ukoliko se objekti u svrhu poredenja smatraju jednakim. Obratite panju na to da ova jednakost ne mora biti ekvivalentna rezultatu izraza o1.equals(o2). Na primer, ako je re c o sortiranju objekata tipa PunoIme, onda je uobi cajeno da se to radi po prezimenima osoba. U tom slu caju, dva puna imena se u svrhu poredenja radi sortiranja smatraju jednakim ukoliko imaju isto prezime, ali to o cigledno ne zna ci da su puna imena dve osobe jednaka. Klasa String implementira interfejs Comparable<String> i denie metod compareTo() za poredenje stringova na razuman na cin. Na osnovu toga moe se denisati i poredenje punih imena tipa PunoIme radi sortiranja:
public class PunoIme implements Comparable<PunoIme> { private String ime, prezime; public int compareTo(PunoIme drugo) { if (prezime.compareTo(drugo.prezime) < 0) return -1; else if (prezime.compareTo(drugo.prezime) > 0) return +1; else return ime.compareTo(drugo.ime);

328

11. G ENERI CKO PROGRAMIRANJE

} . . // Ostali metodi . }

Drugi na cin poredenja objekata u Javi sastoji se od primene posebnog objekta koji direktno izvodi poredenje. Ovaj objekat se naziva komparator i mora pripadati klasi koja implementira interfejs Comparator<T>, gde je T tip objekata koji se uporeduju. U interfejsu Comparator<T> denisan je jedan metod:
public int compare(T o1, T o2)

Ovaj metod uporeduje dva objekta tipa T i treba da vrati broj koji je ne gativan, pozitivan ili nula prema tome da li je objekat o1 manji, ve ci ili jednak objektu o2. Komparatori su korisni u slu cajevima kada treba porediti objekte koji ne implementiraju interfejs Comparable ili kada je potrebno denisati razli cite relacije poretka nad istom kolekcijom objekata. Primitivni tipovi i generi cke kolekcije. Generi ckim kolekcijama u Javi ne mogu pripadati vrednosti primitivnih tipova nego samo klasnih tipova. Ali ovo ograni cenje moe se skoro potpuno prevazi ci upotrebom klasa omota ca primitivnih tipova. Podsetimo se da je za svaki primitivni tip denisan odgovaraju ci klasni tip omota c: za int to je klasa Integer, za boolean to je klasa Boolean, za char to je klasa Character i tako dalje. Tako, jedan objekat tipa Integer sadri vrednost tipa int i slui kao omota c za nju. Na taj na cin se primitivne vrednosti mogu koristiti tamo gde su neophodni objekti u Javi, to je slu caj kod generi ckih kolekcija. Na primer, lista celih brojeva moe biti predstavljena kolekcijom tipa ArrayList<Integer>. U klasi Integer su na prirodan nac in denisani metodi equals(), compareTo() i toString(), a sli cne osobine imaju i druge klase omota ci. Podsetimo se i da se u programu izvodi automatska konverzija izmedu vrednosti primitivnog tipa i objekata odgovaraju ce klase omota ca. Zbog toga u radu sa objektima koji su omota ci primitivnih vrednosti skoro da nema razlike u odnosu na rad s primitivnim vrednostima. To vai i za generi cko programiranje, jer nakon konstruisanja kolekcije sa objektima koji pripadaju omota ckoj klasi nekog primitivnog tipa, ta kolekcija se moe koristiti skoro na isti na cin kao da sadri vrednosti primitivnog tipa. Na primer, ako je k kolekcija tipa Collection<Integer>, onda se mogu koristiti metodi k.add(23) ili k.remove(17). Iako se vrednosti primitivnog tipa int ne mogu dodavati

11.2. Sistem kolekcija u Javi

329

kolekciji k (ili uklanjati iz nje), broj 23 se automatski konvertuje u omota cki objekat new Integer(23) i dodaje kolekciji k. Ovde ipak treba imati u vidu da konverzija uti ce na ekasnot programa tako da je, recimo, obi can niz tipa int[] mnogo ekasniji od kolekcije ArrayList<Integer>. Iteratori. U interfejsu Collection<T> denisani su neki opti metodi koji se mogu primeniti za svaku kolekciju objekata. Postoje medutim i generi cke operacije koje nisu deo tog interfejsa, jer priroda tih operacija zavisi od konkretne implementacije neke kolekcije. Takva je recimo operacija prikazivanja svih objekata u kolekciji. Ova operacija zahteva sistemati cno pose civanje svih objekata u kolekciji nekim redom, po cinju ci od nekog objekta u kolekciji i prelaze ci s jednog objekta na drugi dok se ne iskoriste svi objekti. Ali pojam prelaska s jednog objekta na drugi u kolekciji moe imati razli citu interpretaciju u konkretnoj implemantaciji medusobnih veza objekata kolekcije. Na primer, ako je kolekcija implementirana kao obi can niz objekata, onda je to prosto uve canje indeksa niza za jedan; ako je kolekcija implementirana kao povezana struktura objekata, onda je to pra cenje reference (pokaziva ca) na slede ci objekat. Ovaj problem se u Javi moe reiti na opti na cin pomo cu iteratora. Iterator je objekat koji slui upravo za prelazak s jednog objekta na drugi u kolekciji. Razli cite vrste kolekcija mogu imati iteratore koji su implementirani na razli cite na cine, ali svi iteratori se koriste na isti na cin i zato se njihovom upotrebom dobijaju generi cki postupci. U interfejsu Collection<T> nalazi se metod koji slui za dobijanje iteratora bilo koje kolekcije. Ako je k kolekcija, onda poziv k.iterator() kao rezultat daje iterator koji se moe koristiti za pristup svim objektima kolekcije k. Iterator se moe zamisliti kao jedan oblik generalizovanog pokaziva ca koji najpre ukazuje na po cetak kolekcije i zatim se moe pomerati s jednog objekta na slede ci objekat u kolekciji. Iteratori su denisani parametrizovanim interfejsom Iterator<T>. Ako je k objekat klase koja implementira interfejs Collection<T> za neki speci cni tip T, onda k.iterator() kao rezultat daje iterator tipa Iterator<T>, gde je T isti konkretni tip za koji je implementiran interfejs Collection<T>. U interfejsu Iterator<T> denisana su samo tri metoda. Ako iter ukazuje na objekat klase koja implementira interfejs Iterator<T>, onda se za taj iterator mogu pozivati ovi metodi: iter.next() vra ca slede ci objekat u kolekciji i pomera iterator za jedno mesto. Vra cena vrednost ovog metoda je tipa T. Primetimo da nije mogu ce pristupiti nekom objektu kolekcije bez pomeranja iteratora na

330

11. G ENERI CKO PROGRAMIRANJE

slede ce mesto. Ukoliko su iskori ceni svi objekti kolekcije, poziv ovog metoda proizvodi izuzetak tipa NoSuchElementException. iter.hasNext() vra ca logi cku vrednost ta cno ili neta cno prema tome da li ima preostalih objekata u kolekciji koji nisu kori ceni. U programu se ovaj metod obi cno poziva pre metoda iter.next() da ne bi dolo do izbacivanja izuzetka tipa NoSuchElementException. iter.remove() uklanja objekat koji je vra cen poslednjim pozivom metoda iter.next(). Poziv metoda iter.remove() moe dovesti do izbacivanja izuzetka tipa UnsupportedOperationException ukoliko se iz kolekcije ne mogu uklanjati objekti. Prethodno pomenuti problem prikazivanja objekata svake kolekcije moe se lako generi cki reiti upotrebom iteratora. Na primer, ako je k kolekcija stringova tipa Collection<String>, onda rezultat poziva k.iterator() jeste iterator tipa Iterator<String> za kolekciju k, pa se moe pisati:
// Dobijanje iteratera za kolekciju Iterator<String> iter = k.iterator(); // Pristupanje svakom elementu kolekcije po redu while (iter.hasNext()) { String elem = iter.next(); System.out.println(elem); }

Sli can generi cki postupak moe se primeniti i za druge oblike rada sa kolekcijama. Tako, u narednom primeru se uklanjaju sve vrednosti null iz neke kolekcije k tipa, recimo, Collection<File> (ukoliko je operacija uklanjanja mogu ca u toj kolekciji):
Iterator<File> iter = k.iterator(); while (iter.hasNext()) { File elem = iter.next(); if (elem == null) iter.remove() }

Primena iteratora radi obavljanja neke operacije nad svim objektima kolekcije moe se izbe ci ukoliko se koristi for-each petlja. Pored nizova i nabrojivih tipova, for-each petlja moe se koristiti i za obradu svih objekata neke kolekcije. Za kolekciju k tipa Collection<T>, opti postupak upotrebe for-each petlje ima ovaj oblik:
for (T x : k) { // za svaki objekat x tipa T u kolekciji k .

11.2. Sistem kolekcija u Javi

331

. // Obrada objekta x . }

Kontrolnoj promenljivi x u ovoj petlji redom se dodeljuje svaki objekat iz kolekcije k i zatim se izvrava telo petlje. Poto su objekti u kolekciji k tipa T, promenljiva x mora biti denisana da bude istog tipa T. Na primer, prikazivanje imena svih datoteka u kolekciji dir tipa Collection<File> moe se izvesti na slede ci na cin:
for (File datoteka : dir) { if (datoteka != null) System.out.println(datoteka.getName()); }

Naravno, ekvivalentno reenje ovog primera moe se napisati i upotrebom iteratora i while petlje, ali se kori cenjem for-each petlje dobija razumljiviji program.

Liste
Lista na apstraktnom nivou predstavlja kolekciju objekata koji su linearno uredeni. Sekvencijalni poredak elemenata liste denisan je time to je odreden prvi element liste, iza njega drugi element liste i tako dalje sve do poslednjeg elementa liste. U Javi, ovaj naoptiji koncept liste objekata tipa T obuhva cen je parametrizovanim interfejsom List<T>. Interfejs List<T> nasleduje interfejs Collection<T> i sadri dodatne metode koji omogu ca vaju pristup elementima liste prema njihovoj numeri ckoj poziciji u listi. U nastavku je prikazan spisak ovih metoda za listu l tipa List<T>. Primetimo da njihova funkcionalnost zavisi od metoda size() u interfejsu Collection<T>. ca objekat tipa T koji se nalazi na poziciji i u listi l. Indeks l.get(i) vra i je ceo broj tipa int koji mora biti u intervalu 0,1,2,. . . ,l.size()-1, jer se u suprotnom izbacuje izuzetak tipa IndexOutOfBoundsException. l.set(i,o) zamenjuje objekat na poziciji i u listi l datim objektom o. Objekat o mora biti tipa T. Izvravanjem ovog metoda se ne menja veli cina liste l, niti dolazi do pomeranja njenih drugih elemenata. l.add(i,o) dodaje u listu l dati objekat o na poziciju i. Objekat o mora biti tipa T. Izvravanjem ovog metoda se veli cina liste l uve cava za jedan, a elementi iza pozicije i u listi se pomeraju za jedno mesto udesno da bi se umetnuo novi element. Indeks i mora biti ceo broj u intervalu od 0 do l.size(), a ukoliko je jednak l.size(), novi element se dodaje na kraj liste.

332

11. G ENERI CKO PROGRAMIRANJE

l.remove(i) uklanja iz liste l objekat koji se nalazi na poziciji i i vra ca taj objekat kao rezultat metoda. Izvravanjem ovog metoda se veli cina liste l smanjuje za jedan, a elementi iza pozicije i u listi se pomeraju za jedno mesto ulevo da bi se popunilo mesto uklonjenog elementa. Indeks i mora biti ceo broj u intervalu od 0 do l.size()-1. ca ceo broj tipa int jednak poziciji datog objekta o l.indexOf(o) vra u listi l. Ukoliko se objekat o ne nalazi u listi, vra cena vrednost je 1; ukoliko lista sadri vie objekata o, vra cena vrednost jednaka je poziciji prvog pojavljivanja tog objekta od po cetka liste. Objekat o moe biti bilo kog tipa, ne samo tipa T. Ako je l lista tipa List<T>, onda se pozivom l.iterator() dobija iterator tipa Iterator<T> za pristupanje elementima liste od po cetka do kraja. U interfejsu List<T> dodatno je denisan metod listIterator() koji kao rezultat daje iterator tipa ListIterator<T>. Interfejs ListIterator<T> sadri ne samo uobi cajene metode hasNext(), next() i remove(), nego i metode hasPrevious(), previous() i add(o) koji su dodatno namenjeni samo za rad sa listom elemenata. Ovi dodatni metodi omogu cavaju da se du liste prelazi od jednog do drugog elementa ne samo sleva na desno, nego i zdesna na levo, kao i da se pri tome modikuje lista. Da bi se bolje razumeo na cin rada iteratora za liste, najpre treba imati u vidu da iterator uvek pokazuje bilo izmedu dva elementa u listi ili ispred prvog elementa liste ili iza poslednjeg elementa liste. Ovo je slikovito prikazano na slici 11.1 na kojoj su strelicama nazna cene mogu ce pozicije iteratora izmedu elemenata liste.

Slika 11.1: Pozicije iteratora u listi.

Ako je iter iterator tipa ListIterator<T>, naredbom iter.next() pomera se pokaziva c iteratora za jedno mesto udesno i vra ca element liste preko kojeg je pokaziva c preao. Sli cno, naredbom iter.previous() pomera se pokaziva c iteratora za jedno mesto ulevo i vra ca element liste preko kojeg je pokaziva c preao. Naredbom iter.remove() uklanja se element iz liste koji je vra cen poslednjim pozivom metoda next() ili remove(). Najzad, naredbom iter.add(o) dodaje se dati objekat o, koji mora biti tipa T, u listu na mesto neposredno ispred trenutne pozicije pokaziva ca iteratora.

11.2. Sistem kolekcija u Javi

333

Radi prakti cne ilustracije rada sa iteratorom liste, razmotrimo problem sortirane liste stringova u rastu cem redosledu i dodavanja novog stringa u tu listu tako da proirena lista bude i dalje sortirana. U nastavku je prikazan metod u kome se koristi iterator tipa ListIterator za nalaenje pozicije u listi sortiranih stringova l na kojoj treba dodati novi string s. U ovom metodu, pokaziva c tog iteratora se pomera od po cetka liste l za jedno mesto preko svih elemenata koji su manji od novog elementa s. Kada se pri tome naide na prvi element liste koji je ve ci od novog elementa, na tom mestu se u listi dodaje novi element koriste ci metod add() iteratora liste.
public void dodajSortListi(List<String> l, String s) { ListIterator<String> iter = l.listIterator(); while (iter.hasNext()) { String elem = iter.next(); if (s.compareTo(elem) <= 0) { // Novi string s treba da bude ispred elementa elem, // ali je pokaziva c iteratora pomeren iza tog elementa. iter.previous(); break; } } iter.add(s); }

Klase ArrayList i LinkedList. Prakti cna reprezentacija opteg pojma liste elemenata u Javi i denicija metoda u interfejsu List<T> zasniva se na dinami ckim nizovima i povezanim c vorovima. Ova dva na cina obuhva cena su dvema konkretnim klasama, ArrayList i LinkedList, koje su deo Sistema kolekcija u Javi. Parametrizovane klase ArrayList<T> i LinkedList<T>, koje se nalaze u paketu java.util, predstavljaju listu elemenata pomo cu dinami ckog niza i povezanih c vorova. Obe klase implementiraju interfejs List<T>, a time i Collection<T>. Jedan objekat tipa ArrayList<T> predstavlja linearno uredenu listu objekata tipa T koji se c uvaju kao elementi dinami ckog niza, od nosno duina niza se automatski pove cava kada je to neophodno prilikom dodavanja novih objekata u listu. Jedan objekat tipa LinkedList<T> takode predstavlja linearno uredenu listu objekata tipa T, ali se ti objekti c uvaju u formi c vorova koji su medusobno povezani pokaziva cima (referencama). Kako obe klase ArrayList<T> i LinkedList<T> implementiraju interfejs List<T>, odnosno omogu cavaju iste osnovne operacije nad listom, postavlja

334

11. G ENERI CKO PROGRAMIRANJE

se pitanje zato su potrebne dve implementacije liste umesto samo jedne od njih? Odgovor lei u ekasnosti pojedinih operacija u dvema reprezentacijama liste. Naime, za neke operacije su povezane liste ekasnije od nizova, dok za druge operacije nad listom vai suprotno. Zato u konkretnim primenama listi treba paljivo odmeriti koje se operacije c esto koriste i prema tome odabrati optimalnu implementaciju liste. Povezana lista predstavljena klasom LinkedList<T> ekasnija je u primenama kod kojih se elementi liste c esto dodaju ili uklanjaju u sredini liste. Ove operacije kod liste predstavljene dinami ckim nizom tipa ArrayList<T> zahtevaju pomeranje priblino polovine elementa niza udesno ili ulevo radi pravljenja mesta za dodavanje novog elementa ili popunjavanje praznine nakon uklanjanja nekog elementa. S druge strane, lista predstavljena dinami ckim nizom ekasnija je kada treba pro citati ili promeniti proizvoljni element liste. U takvom slu caju se traenom elementu liste pristupa prostim kori cenjem njegovog indeksa u dinami ckom nizu, dok se kod povezane liste moraju redom ispitati svi elementi od po cetka do, u najgorem slu caju, kraja liste. Zbog toga, mada klase ArrayList<T> i LinkedList<T> implementiraju sve metode u interfejsu List<T>, neki od tih metoda ekasni su samo za pojedinu reprezentaciju liste. U klasi LinkedList<T> se nalaze neki metodi koji nisu denisani u klasi ArrayList<T>. Tako, ako je ll lista tipa LinkedList<T>, onda se za taj objekat mogu koristiti i ovi metodi: ll.getFirst() vra ca objekat tipa T koji se nalazi na samom po cetku liste ll. Pri tome lista ostaje nepromenjena, odnosno prvi element liste se ne uklanja. A ukoliko je lista prazna, izbacuje se izuzetak tipa NoSuchElementException. ll.getLast() vra ca objekat tipa T koji se nalazi na samom kraju liste ll. Pri tome lista ostaje nepromenjena, odnosno poslednji element liste se ne uklanja. A ukoliko je lista prazna, izbacuje se izuzetak tipa NoSuchElementException. ll.removeFirst() uklanja prvi element liste ll i vra ca taj objekat tipa T kao rezultat. Izuzetak tipa NoSuchElementException izbacuje se ukoliko je lista prazna. ll.removeLast() uklanja poslednji element liste ll i vra ca taj objekat tipa T kao rezultat. Ukoliko je lista prazna, izbacuje se izuzetak tipa NoSuchElementException. ll.addFirst(o) dodaje novi objekat o, koji mora biti tipa T, na po cetak liste ll.

11.2. Sistem kolekcija u Javi

335

ll.addLast(o) dodaje novi objekat o, koji mora biti tipa T, na kraj liste ll.

Primer: sortiranje liste


Kao to je poznato, u klasi Arrays iz paketa java.util nalazi se stati cki metod sort() kojim se moe ekasno sortirati standardni niz elemenata u rastu cem redosledu. Tako, naredbom
Arrays.sort(a);

sortira se niz a. Elementi niza a mogu biti jednog od primitivnih tipova (osim boolean) ili mogu biti objekti klase koja implementira interfejs Comparable. Svi metodi za ekasno sortiranje niza zasnivaju se na c injenici da se svakom elementu niza moe lako pristupiti preko njegovog indeksa. Postavlja se pitanje da li se opta lista elemenata moe ekasno sortirati s obzirom na to da pristup svim elementima liste nije vremenski podjednak na primer, da bi se koristio neki element u sredini liste moraju se redom pro ci svi elementi liste od njenog po cetka do tog elementa. To je sutinski razli cito od na cina na koji se koriste elementi niza, gde je vreme pristupa prvom, srednjem ili bilo kom elementu niza potpuno jednako zahvaljuju ci indeksima. Jedan o cigledan postupak za sortiranje liste je da se od date liste obrazuje niz kopiraju ci redom elemente liste u jedan niz, zatim da se ekasno sortira novoformirani niz i, na kraju, da se od sortiranog niza obrazuje sortirana lista kopiraju ci redom elemente niza. U stvari, sli can na cin se koristi u stati ckom metodu sort() u klasi Collections iz paketa java.util. Ova klasa sadri razne pomo cne metode za rad sa optim kolekcijama, pa i listama. Tako, ukoliko je l lista tipa List<T>, naredbom
Collections.sort(l);

sortira se lista l u rastu cem redosledu. Elementi liste l moraju biti objekti klase koja implementira interfejs Comparable<T>. Da bismo pokazali da se lista elemenata moe ekasno i direktno sortirati, u ovom primeru govorimo o postupku za sortiranje liste koji se naziva sortiranje objedinjavanjem (engl. merge sort ). Ovaj postupak je po prirodi rekurzivan i sastoji se od tri glavna koraka: 1. Data lista se najpre deli na dva dela u dve duplo manje liste
1 1

2.

2. Ove manje liste

se zatim rekurzivno sortiraju istim postupkom.

3. Sortirane manjih lista 1 i 2 dobijene u prethodnom koraku na kraju se objedinjuju u jednu sortiranu listu .

336

11. G ENERI CKO PROGRAMIRANJE

Ekasnost celog postupka sortiranja objedinjavanjem zasniva se upravo na brzini njegovog tre ceg koraka. (Prvi korak se o cigledno moe brzo izvriti alternativnim kopiranjem elemenata date liste u prvu ili drugu manju listu.) Naime, dve sortirane manje liste mogu se ekasno objediniti u jednu sortiranu listu sekvencijalnim uparivanjem elemenata prve i druge liste i kopiranjem manjeg elementa iz odgovaraju ce liste u rezultuju cu listu. U nastavku je prikazan metod u kome se primenjuje ovaj pristup za sortiranje liste. Radi konkretnosti, uzeto je da se lista sastoji od celih brojeva, mada se metod moe lako prilagoditi za listu c iji su elementi bilo kog konkretnog tipa koji implementira interfejs Comparable<T>. Jo bolje, metod se moe napisati tako da bude generi cki metod kako bi se mogao koristiti za liste parametrizovanog tipa. O tome se govori pri kraju ovog poglavlja na strani 362.
public static void mergeSort(List<Integer> l) { if (l.size() > 1) { // lista l ima bar dva elementa

// 1. Podeliti listu l u dve polovine l1 i l2 List<Integer> l1 = new LinkedList<Integer>(); List<Integer> l2 = new LinkedList<Integer>(); ListIterator<Integer> it = l.listIterator(); boolean dodajL1 = true; // element liste l ide u l1 ili l2 while (it.hasNext()) { Integer e = it.next(); if (dodajL1) l1.add(e); else l2.add(e); dodajL1 = !dodajL1; } // 2. Rekurzivno sortirati liste l1 i l2 mergeSort(l1); mergeSort(l2); // 3. Objediniti sortirane liste l1 i l2 u sortiranu listu l l.clear(); ListIterator<Integer> it1 = l1.listIterator(); ListIterator<Integer> it2 = l2.listIterator(); Integer e1 = it1.next(); Integer e2 = it2.next(); // l1 ima bar jedan element // l2 ima bar jedan element

11.2. Sistem kolekcija u Javi

337

while (true) { if (e1.compareTo(e2) <= 0) { l.add(e1); if (it1.hasNext()) e1 = it1.next(); else { l.add(e2); while (it2.hasNext()) l.add(it2.next()); break; } } else { l.add(e2); if (it2.hasNext()) e2 = it2.next(); else { l.add(e1); while (it1.hasNext()) l.add(it1.next()); break; } } } } }

Skupovi
Skup je kolekcija objekata u kojoj nema duplikata, odnosno nijedan objekat u skupu se ne pojavljuje dva ili vie puta. U Javi, ovaj naoptiji koncept skupa objekata tipa T obuhva cen je parametrizovanim interfejsom Set<T>. Interfejs Set<T> nasleduje interfejs Collection<T> i obezbeduje da se nije dan objekat ne pojavljuje dvaput u skupu. Tako, ako je s objekat tipa Set<T>, onda naredba s.add(o) ne proizvodi nikakav efekat ukoliko se objekat o ve c nalazi u skupu s. Prakti cna reprezentacija opteg pojma skupa elemenata u Javi i denicija metoda u interfejsu Set<T> zasniva se na binarnim stablima i he tabelama. Ova dva na cina obuhva cena su dvema konkretnim generi ckim klasama TreeSet<T> i HashSet<T> u paketu java.util. Skup predstavljen klasom TreeSet ima dodatnu osobinu da su njegovi elementi uredeni u rastu cem redosledu. Pored toga, iterator za takav skup uvek prolazi kroz elemente skupa u ovom rastu cem redosledu. Ovo sa druge strane ograni cava vrstu objekata koji mogu pripadati skupu tipa TreeSet, jer

338

11. G ENERI CKO PROGRAMIRANJE

se njegovi objekti moraju uporedivati da bi se odredio njihov rastu ci redosled. Prakti cno to zna ci da objekti u skupu tipa TreeSet<T> moraju implementirati interfejs Comparable<T> tako da relacija o1.compareTo(o2) bude denisana na razuman na cin za svaka dva objekta o1 i o2 u skupu. Alternativno, kada se konstruie skup tipa TreeSet<T> moe se kao parametar konstruktora navesti objekat tipa Comparator<T>. U tom slu caju se za poredenje objekata u konstruisanom skupu koristi metod compare() komparatora. jednakosti U klasi TreeSet se ne koristi metod equals() za utvrdivanje dva objekta u skupu, nego metod compareTo() (ili compare()). To ponekad moe dovesti do nelogi cnosti, jer jednakost objekata na osnovu rezultata metoda compareTo() ne mora biti ekvivalentna njihovoj prirodnoj jednakosti koja se dobija kao rezultat metoda equals(). Na primer, dva objekta koji predstavljaju adrese prirodno su jednaka ukoliko su svi delovi adresa jednaki, odnosno jednaki su i ulica i broj i grad i potanski broj. Ako bi se adrese uporedivale metodom compareTo() samo na osnovu, recimo, potanskog broja, onda bi se sve adrese sa istim potanskim brojem smatrale jednakim. To bi onda zna cilo da bi se u skupu tipa TreeSet mogla nalaziti samo jedna adresa sa odredenim potanskim brojem, to verovatno nije pravi na cin za predstavljanje skupa adresa. Ovaj potencijalni problem u radu sa skupovima tipa TreeSet treba imati na umu i stoga treba obezbediti da metod compareTo() bude denisan na razuman na cin za objekte konkretnog skupa. Sre com, o ovome ne treba brinuti za objekte standardnih tipova String, Integer i mnogih drugih, jer se za njih poklapa prirodna jednakost i ona dobijena na osnovu metoda compareTo(). Cinjenica da su elementi skupa tipa TreeSet sortirani i da takav skup ne sadri duplikate moe biti vrlo korisna u nekim primenama. Pretpostavimo da je, recimo, k neka kolekcija stringova tipa Collection<String>. (Tip objekata String u kolekciji nije bitan, nego to da je za njega pravilno denisan metod compareTo().) Ako je potrebno sortirati objekte u kolekciji k i ukloniti duplikate u njoj, to se moe uraditi na jednostavan na cin pomo cu jednog skupa tipa TreeSet:
TreeSet<String> s = new TreeSet<String>(); s.addAll(k);

Drugom naredbom u ovom primeru se svi objekti kolekcije k dodaju skupu s. Pri tome, kako je s tipa TreeSet, duplikati u skupu s se uklanjaju i elementi tog skupa se ureduju u rastu cem redosledu. Elementi skupa mogu se lako dodeliti nekoj drugoj strukturi podataka. Tako, nastavljaju ci prethodni primer, od elemenata skupa s moe se formirati sortirana lista tipa ArrayList<String>:

11.2. Sistem kolekcija u Javi

339

TreeSet<String> s = new TreeSet<String>(); s.addAll(k); ArrayList<String> l = new ArrayList<String>(); l.addAll(s);

U stvari, svaka klasa u Sistemu kolekcija u Javi poseduje konstruktor koji ima parametar tipa Collection. Ukoliko se koristi taj konstruktor za konstruisanje nove kolekcije, svi elementi kolekcije koja je navedena kao argument konstruktora dodaju se inicijalno novokonstruisanoj kolekciji. Na primer, ako je k tipa Collection<String>, onda se izrazom
new TreeSet<String>(k)

konstruie skup tipa TreeSet<String> koji ima iste elemente kao kolekcija k, ali bez duplikata i sa sortiranim elementima. To zna ci da se c etiri naredbe prethodnog primera za formiranje sortirane liste stringova mogu kra ce zapisati koriste ci samo jednu naredbu:
ArrayList<String> l = new ArrayList<String>(new TreeSet<String>(k));

Potpuno drugi na cin predstavljanja skupa elemenata u Javi sproveden je u klasi HashSet. U ovom slu caju se elementi skupa c uvaju u strukturi podataka koja se naziva he tabela. Bez ulaenja u mnogo detalja, osnovna odlika ove strukture podataka jeste da ona obezbeduje vrlo ekasne operacije nalaenja, dodavanja i uklanjanja elemenata. Te operacije su mnogo bre, u proseku, od sli cnih operacije za skupove predstavljene klasom TreeSet. S druge strane, objekti nekog skupa tipa HashSet nisu uredeni u posebnom redosledu i ne moraju da implementiraju interfejs Comparable. Zato jedan iterator za skup tipa HashSet obilazi elemente tog skupa u nepredvidljivom redosledu, koji c ak moe biti potpuno druga ciji nakon dodavanja novog elementa skupu. Za odredivanje jednakosti dva elementa u skupu tipa HashSet koristi se metod equals(). Prema tome, ekasniju klasu HashSet u odnosu na klasu TreeSet treba koristiti za predstavljanje skupa elementa ukoliko za njegove elemente nije denisana relacija poredenja ve ce ili manje, ili to nije vano u konkretnoj primeni.

Mape
Mapa je generalizacija ra cunarskog koncepta niza elemenata, odnosno realizacija matemati ckog pojma preslikavanja ili funkcije. Naime, niz a od n elemenata moe se smatrati preslikavanjem brojeva 0, 1, . . . , n 1 u odgovaraju ce elemente niza: ako je i neki od ovih brojeva, njegova slika je element a i . Dve fundamentalne operacije nad nizom elemenata su get, koja za dati

340

11. G ENERI CKO PROGRAMIRANJE

indeks i vra ca vrednost elementa a i , kao i put, koja datu novu vrednost za dati indeks i upisuje kao novu vrednost elementa a i . Po ovoj analogiji, mapa je odredena preslikavanjem objekata proizvoljnog tipa T, a ne celih brojeva 0, 1, . . . , n 1 kao kod niza, u objekte potencijalno razli citog tipa S. Isto tako, osnovne operacije get i put analogno se proiruju nad objektima tipa T kojima su pridrueni objekti tipa S. Prema tome, mapa koncepcijski podse ca na niz, osim to za indekse ne slue celi brojevi nego objekti proizvoljnog tipa. Objekti koji u mapi slue kao indeksi nazivaju se klju cevi, dok se objekti koji su pridrueni klju cevima nazivaju vrednosti. Jedna mapa moe se dakle smatrati skupom pridruivanja, pri c emu je svako pridruivanje odredeno parom klju c/vrednost. Obratite panju na to da svaki klju c moe odgovarati najvie jednoj vrednosti, ali jedna vrednost moe biti pridruena ve cem broju razli citih klju ceva. U Javi, mape su predstavljene interfejsom Map<T,S> iz paketa java.util. Ovaj interfejs je parametrizovan dvoma tipovima: prvi parametar T odreduje tip objekata koji predstavljaju klju ceve mape, a drugi parametar S odreduje tip objekata koji su vrednosti mape. Mapa tipa Map<Date,Boolean>, na primer, denie preslikavanje klju ceva tipa Date u vrednosti tipa Boolean. U mapi tipa Map<String,String> klju cevi i vrednosti jesu istog tipa String. U interfejsu Map<T,S> nalaze se metodi get() i put(), kao i drugi opti metodi za rad sa mapama. Tako, ako je m promenljiva tipa Map<T,S> za neke speci cne tipove T i S, onda se mogu koristiti slede ci metodi: m.get(k) vra ca objekat tipa S koji je pridruen datom klju cu k u mapi m. Klju c k ne mora biti objekat isklju civo tipa T, nego moe biti bilo koji objekat. Ako nijedna vrednost nije pridruena klju cu k u mapi, vra cena vrednost je null. Primetimo da je efekat zapisa m.get(k) sli can rezultatu zapisa a[k] za obi can niz a ukoliko je k indeks tog niza. m.put(k,v) u mapi m pridruuje datu vrednost v datom klju cu k. Klju c k mora biti tipa T i vrednost v mora biti tipa S. Ako mapa ve c sadri par sa datim klju cem, onda u tom paru nova vrednost zamenjuje staru. Ovo je sli cno efektu naredbe a[k] = v za obi can niz a. m.remove(k) uklanja par klju c/vrednost u mapi m koji odgovara datom klju cu k. Klju c k ne mora biti objekat isklju civo tipa T, nego moe biti bilo koji objekat. m.clear() uklanja sve parove klju c/vrednost koje sadri mapa m. m.containsKey(k) vra ca logi cku vrednost ta cno ukoliko mapa m sadri par klju c/vrednost koji odgovara datom klju cu k. Klju c k ne mora biti objekat isklju civo tipa T, nego moe biti bilo koji objekat.

11.2. Sistem kolekcija u Javi

341

m.containsValue(v) vra ca logi cku vrednost ta cno ako je data vrednost v pridruena nekom klju cu u mapi m. Vrednost v ne mora biti objekat isklju civo tipa S, nego moe biti bilo koji objekat. m.size() vra ca celobrojnu vrednost tipa int koja predstavlja broj parova klju c/vrednost u mapi m. m.isEmpty() vra ca logi cku vrednost ta cno ukoliko je mapa m prazna, odnosno ne sadri nijedan par klju c/vrednost. c/vrednost koje sadri m.putAll(mm) prepisuje u mapu m sve parove klju druga mapa mm tipa Map<T,S>. cene su Dve prakti cne implementacije interfejsa Map<T,S> u Javi obuhva klasama TreeMap<T,S> i HashMap<T,S>. Parovi klju c/vrednost u mapi koja je predstavljena klasom TreeMap c uvaju se u strukturi binarnog stabla. Pri tome su parovi mape sortirani prema njihovim klju cevima, te zato ovi klju cevi moraju biti uporedivi. To zna ci da bilo tip klju ceva T mora implementirati interfejs Comparable<T> ili se mora navesti komparator za poredenje klju ceva kao parametar konstruktora klase TreeMap. Obratite panju na to da se, sli cno skupu tipa TreeSet, za mapu tipa TreeMap koristi metod compareTo() radi utvrdivanja da li su dva klju ca jednaka. Ovo treba imati na umu prilikom kori cenja mape tipa TreeMap, jer ukoliko se prirodna jednakost klju ceva ne podudara sa onom koju indukuje metod compareTo(), to moe imati iste neeljene posledice o kojima smo govorili kod skupova. U drugoj implementaciji, parovi klju c/vrednost u mapi koja je predstavljena klasom HashMap c uvaju se u he tabeli. Zbog toga parovi takve mape nisu uredeni po nekom posebnom redosledu, odnosno klju cevi takve mape ne moraju biti uporedivi. Ali s druge strane, klasa klju ceva mora imati dobre denicije metoda equals() i hashCode(), to je obezbedeno u svim standard nim klasama u Javi. Ve cina operacija u radu sa mapom neto je ekasnija ukoliko se koristi klasa HashMap u odnosu na klasu TreeMap. Zato je obi cno bolje koristiti klasu HashMap ukoliko u programu nema potrebe za sortiranim redosledom kljuc eva koji obezbeduje klasa TreeMap. Speci cno, ukoliko se u programu koriste samo operacije get i put za neku mapu, dovoljno je koristiti klasu HashMap.

Primer: telefonski imenik kao mapa


Jedan dobar primer koncepta mape u programiranju je struktura podataka koju obrazuje telefonski imenik. Telefonski imenik je kolekcija stavki koje se sastoje od dva podatka: imena osobe i njenog telefonskog broja. Osnovne operacije nad telefonskim imenikom su:

342

11. G ENERI CKO PROGRAMIRANJE

Dodavanje nove stavke (imena osobe i telefonskog broja) u imenik. Uklanjanje jedne stavke sa datim imenom osobe iz imenika. Nalaenje telefonskog broja u imeniku za dato ime osobe. Telefonski imenik kao struktura podataka moe se prosto realizovati kao dinami cki niz elemenata, pri c emu svaki element niza predstavlja jednu stavku imenika. Za takav niz onda osnovne operacije nad telefonskim imenikom predstavljaju obi cne operacije dodavanja, uklanjanja i nalaenja elemenata jednog niza. Elegantnije reenje, medutim, moe se dobiti ukoliko se primeti da je te lefonski imenik upravo jedna mapa kojom su telefonski brojevi pridrueni imenima osoba. U nastavku je prikazana klasa u kojoj je imenik predstavljen mapom tipa Map<String,String>. Dodavanje nove stavke u imenik je onda obi cna operacija put za mapu, dok nalaenje telefonskog broja u imeniku odgovara operaciji get.
import java.util.*; public class TelImenik { private Map<String,String> imenik; // Konstruktor public TelImenik() { imenik = new HashMap<String,String>(); } public String na diBroj(String imeOsobe) { return imenik.get(imeOsobe); } public void dodajStavku(String imeOsobe, String brojOsobe) { imenik.put(imeOsobe,brojOsobe); } public void ukloniStavku(String imeOsobe) { imenik.remove(imeOsobe); } }

Pogledi na mape i kolekcije


Tehni cki jedna mapa nije kolekcija i zato za mape nisu denisane sve operacije koje su mogu ce za kolekcije. Speci cno, za mape nisu denisani itera-

11.2. Sistem kolekcija u Javi

343

tori. S druge strane, u primenama je c esto potrebno redom obraditi sve parove klju c/vrednost koje sadri jedna mapa. U Javi se ovo moe uraditi na indirektan na cin preko takozvanih pogleda. Ako je m promenljiva tipa Map<T,S>, onda se pozivom metoda keySet() za mapu m
m.keySet()

dobija skup objekata koje c ine klju cevi u svim parovima mape m. Objekat koji se vra ca pozivom metoda keySet() implementira interfejs Set<T>. To je dakle skup c iju su elementi svi klju cevi mape. Metod keySet() je speci can po tome to njegov rezultat nije nezavisan skupovni objekat tipa Set<T>, nego se pozivom m.keySet() dobija pogled na aktuelne klju ceve koji se nalaze u mapi m. Ovaj pogled na mapu implementira interfejs Set<T>, ali na poseban na cin tako da metodi denisani u tom interfejsu direktno manipuliu klju cevima u mapi. Tako, ukoliko se ukloni neki klju c iz pogleda, taj klju c (zajedno sa pridruenom vredno cu) zapravo se uklanja iz same mape. Sli cno, nije dozvoljeno dodavanje klju ca nekom pogledu, jer dodavanje klju ca mapi bez njegove pridruene vrednosti nije mogu ce. S druge strane, kako se pozivom m.keySet() ne konstruie novi skup, metod keySet() je vrlo ekasan. Budu ci da su za skupove denisani iteratori, iterator za skup klju ceva u mapi moe se iskoristiti da bi se redom obradili svi parovi koje sadri mapa. Na primer, ako je m tipa Map<String,Double>, onda se na slede ci na cin mogu prikazati svi parovi klju c/vrednost u mapi m:
Set<String> klju cevi = m.keySet(); // skup klju ceva mape Iterator<String> klju cIter = klju cevi.iterator(); System.out.println("Mapa sadri slede ce parove klju c/vrednost:"); //Redom za svaki klju c u skupu klju ceva mape prikazati (klju c,vrednost) while (klju cIter.hasNext()) { String klju c = klju cIter.next(); // slede ci klju c u mapi Double vrednost = m.get(klju c); // vrednost pridruena tom klju cu System.out.println("(" + klju c + "," + vrednost + ")"); }

Bez kori cenja iteratora, isti zadatak moe se elegantnije reiti pomo cu for-each petlje:
System.out.println("Mapa sadri slede ce parove klju c/vrednost:"); //Redom za svaki klju c u skupu klju ceva mape prikazati (klju c,vrednost) for (String klju c : m.keySet()) { Double vrednost = m.get(klju c); System.out.println("(" + klju c + "," + vrednost + ")"); }

344

11. G ENERI CKO PROGRAMIRANJE

Ako je mapa tipa TreeMap, onda su klju cevi u mapi sortirani i zato se kori cenjem iteratora za skup klju ceva mape dobijaju klju cevi u rastu cem redosledu. Za mapu tipa HashMap klju cevi se dobijaju u proizvoljnom, nepredvidljivom redosledu. U interfejsu Map<T,S> denisana su jo dva pogleda. Ako je m promenljiva tipa Map<T,S>, prvi pogled se dobija metodom
m.values()

koji vra ca objekat tipa Collection<S> koji sadri sve vrednosti iz parova u mapi m. Ovaj objekat je kolekcija a ne skup, jer rezultat moe sadrati duplikate elemenata poto u mapi ista vrednost moe biti pridruena ve cem broju klju ceva. Drugi pogled se dobija metodom
m.entrySet()

koji daje skup c iji su elementi svi parovi klju c/vrednost u mapi m. Ovi elementi su objekti tipa Map.Entry<T,S> koji je denisan kao stati cki ugnjedeni in terfejs unutar interfejsa Map<T,S>. Zbog toga se njegovo puno ime zapisuje ta cka-notacijom, ali to ne zna ci da se ne moe koristiti na isti na cin kao bilo koje drugo ime tipa. Primetimo da je rezultat metoda m.entrySet() skup c iji su elementi tipa Map.Entry<T,S>. Ovaj skup je dakle tipa
Set<Map.Entry<T,S>>

odnosno parametar tipa u ovom slu caju je i sam jedan parametrizovan tip. Skup koji je rezultat poziva metoda m.entrySet() nosi zapravo iste informacije o parovima klju c/vrednost kao i mapa m, ali taj skup prua razli cit pogled na njih obezbeduju cije operacije. Svaki element ovog skupa ci druga je objekat tipa Map.Entry koji sadri jedan par klju c/vrednost u mapi. U interfejsu Map.Entry denisani su metodi getKey() i getValue() za dobijanje klju ca i vrednosti koji c ine par, kao i metod setValue(v) za promenu vrednosti odredenog para u novu vrednost v. Obratite panju na to da se kori cenjem metoda setValue(v) za objekat tipa Map.Entry modikuje sama mapa, odnosno to je ekvivalentno kori cenju metoda put() za mapu uz odgovaraju ci klju c. Radi ilustracije, prikazivanje svih parova klju c/vrednost u mapi moe se ekasnije reiti primenom skupovnog pogleda na mapu nego izdvajanjem njenog skupa klju ceva, kao to je to uradeno u prethodnom primeru. Razlog ve ce ekasnosti ovog pristupa je u tome to se ne mora koristiti metod get() radi traenja vrednosti koja je pridruena svakom klju cu. Ako je mapa m tipa Map<String,Double> kao u prethodnom primeru, onda se prikazivanje njenog sadraja moe ekasno izvesti na slede ci na cin:

11.2. Sistem kolekcija u Javi

345

Set<Map.Entry<String,Double>> parovi = m.entrySet(); Iterator<Map.Entry<String,Double>> parIter = parovi.iterator(); System.out.println("Mapa sadri slede ce parove klju c/vrednost:"); //Redom za svaki par u mapi prikazati (klju c,vrednost) while (parIter.hasNext()) { Map.Entry<String,Double> par = parIter.next(); String klju c = par.getKey(); // izdvojiti klju c iz para Double vrednost = par.getValue(); // izdvojiti vrednost iz para System.out.println("(" + klju c + "," + vrednost + ")"); }

Naravno, isti zadatak se moe kra ce reiti for-each petljom:


System.out.println("Mapa sadri slede ce parove klju c/vrednost:"); //Redom za svaki par u mapi prikazati (klju c,vrednost) for (Map.Entry<String,Double> par : m.entrySet()) System.out.println("(" + par.getKey() + "," + par.getValue() + ")");

Treba naglasiti da se u Javi mogu koristiti pogledi ne samo za mape, nego i za neke vrste kolekcija. Tako, podlista u interfejsu List<T> denisana je kao pogled na deo liste. Ako je l lista tipa List<T>, onda se metodom
l.subList(i,j)

dobija pogled na deo liste l koji obuhvata elemente koji se nalaze na pozicijama izmedu cno, podlista sadri celobrojnih indeksa i i j. (Moda malo neobi i -ti element ali ne sadri j -ti element liste.) Ovaj pogled omogu cava rad sa podlistom na isti na cin kao sa obi cnom listom, odnosno mogu se koristiti sve operacije koje su denisane za listu. Ta podlista, medutim, nije neka nezavisna lista, jer promene izvrene nad elementima podliste zapravo se odraavaju na originalnu listu. Pogledi se mogu obrazovati i radi predstavljanja odredenih podskupova sortiranih skupova. Ako je s skup tipa TreeSet<T>, onda se metodom
s.subSet(e1,e2)

dobija podskup tipa Set<T> koji sadri sve elemente skupa s koji se nalaze izmedu elemenata e1 i e2. (Preciznije, podskup sadri sve one elemente skupa s koji su ve ci ili jednaki elementu e1 i koji su striktno manji od elementa e2.) Parametri e1 i e2 moraju biti tipa T. Na primer, ako je knjige skup tipa TreeSet<String> koji sadri naslove svih knjiga u nekoj biblioteci, onda se izrazom knjige.subSet("M","N") obrazuje podskup svih knjiga c iji naslovi po cinju slovom M. Ovaj podskup predstavlja pogled na originalni skup, odnosno kod formiranja podskupa ne dolazi do kopiranja elemenata skupa. Isto tako, sve promene izvrene nad podskupom, kao to su dodavanje ili uklanjanje elemenata, reektuju se zapravo na originalni skup.

346

11. G ENERI CKO PROGRAMIRANJE

Za skupove se mogu koristiti jo dva pogleda. Podskup s.headSet(e) sadri sve elemente skupa s koji su striktno manji od elementa e, dok podskup s.tailSet(e) sadri sve elemente skupa s koji su ve ci ili jednaki elementu e. Pored pogleda za opte mape koji su pomenuti na po cetku ovog odeljka, za mape tipa TreeMap<T,S> sa sortiranim klju cevima mogu se koristiti jo tri pogleda kojima se obrazuju podmape date mape. Podmapa je konceptualno sli cna podskupu i predstavlja podskup klju ceva originalne mape zajedno sa pridruenim vrednostima. Ako je m mapa tipa TreeMap<T,S>, onda se metodom
s.subMap(k1,k2)

dobija podmapa koja sadri sve parove klju c/vrednost iz mape m c iji su kljuc evi ve ci ili jednaki klju cu k1 i striktno su manji od klju ca k2. Druga dva pogleda m.headMap(k) i m.tailMap(k) za mape denisani su na sli can na cin kao pogledi headSet i tailSet za skupove. Da bismo ilustrovali rad sa podmapama, pretpostavimo da je imenik mapa tipa TreeMap<String,String> c iji su klju cevi imena osoba a vrednosti su njihovi telefonski brojevi. Prikazivanje telefonskih brojeva svih osoba iz imenika c ije ime po cinje slovom M moe se izvesti na slede ci na cin:
Map<String,String> mParovi = imenik.subMap("M","N"); if (mParovi.isEmpty()) { System.out.println("Nema osobe cije ime po cinje na M."); } else { System.out.println("Tel. brojevi osoba cija imena po cinju na M:"); for (Map.Entry<String,String> par : mParovi.entrySet()) System.out.println(par.getKey() + ": " + par.getValue()); }

Primer: pravljenje indeksa knjige


Objekti neke kolekcije ili mape mogu biti bilo kog tipa. To zna ci da elementi kolekcije ili mape mogu i sami biti kolekcije ili mape. Indeks knjige je prirodan primer jedne takve sloene strukture podataka. Indeks u knjizi se sastoji od liste vanijih izraza koji se pojavljuju u knjizi. Radi lakeg nalaenja ovih izraza u knjizi, uz svaki izraz se nalazi lista njegovih referenci, tj. lista brojeva strana knjige na kojima se taj izraz pojavljuje. Ukoliko se indeks programski formira skeniranjem teksta knjige, potrebno je indeks predstaviti strukturom podataka koja omogu cava ekasno dodavanje eljenog izraza u indeks. Na kraju, formirani indeks treba prikazati (odtampati) tako da njegovi izrazi budu prikazani po azbu cnom redu.

11.2. Sistem kolekcija u Javi

347

S obzirom na mnogo detalja celog problema pravljenja indeksa knjige koji mogu zakloniti sutinu, ovde c emo razmotriti samo bitne delove reenja ovog problema u Javi primenom generi ckih struktura podataka. Kako su izrazi u knjizi medusobno razli citi, indeks se moe predstaviti mapom u kojoj se lista referenci pridruuje svakom izrazu. Izrazi su dakle klju cevi mape, a vrednosti pridruene tim klju cevima su liste referenci za odgovaraju ci izraz. Zahtev da prikazivanje formiranog indeksa bude po azbu cnom redu izraza ostavlja jedini izbor u pogledu tipa mape koja predstavlja indeks: TreeMap. Ako se podsetimo, tip TreeMap za razliku od tipa HashMap obezbeduje lako dobijanje klju ceva mape u sortiranom redosledu. Vrednost pridruena svakom klju cu mape koja reprezentuje indeks knjige treba da bude lista brojeva strana za odgovaraju ci izraz. Brojevi strana u svakoj ovoj listi su medusobno razli citi, tako da je to zapravo skup brojeva, a ne lista u smislu strukture podataka. Pored toga, brojevi strana za svaki izraz se prikazuju u rastu cem redosledu, to zna ci da ovaj skup brojeva treba da bude tipa TreeSet. Brojevi u ovom skupu su primitivnog tipa int, ali poto elementi generi cke strukture podataka u Javi moraju biti objekti, za brojeve skupa se mora koristiti omota cka klasa Integer. Sve u svemu, indeks knjige se predstavlja mapom tipa TreeMap c ije kljuc eve c ine izrazi tipa String, a odgovaraju ce vrednosti klju ceva c ine skupovi brojeva tipa TreeSet<Integer>. Indeks knjige je dakle mapa tipa
TreeMap<String,TreeSet<Integer>>

Formiranje indeksa knjige u programu po cinje od prazne mape koja predstavlja indeks. Zatim se redom c ita tekst knjige i usput se ovoj mapi dodaje svaki vaniji izraz i aktuelni broj strane knjige na kojoj se izraz otkrije. Na kraju, nakon pregledanja cele knjige, sadraj mape se prikazuje ili tampa ili upisuje u datoteku. Razmotrimo kako se svaki od ova tri koraka moe realizovati u programu ukoliko zanemarimo pitanje otkrivanja vanijih izraza koji se dodaju u indeks. Za taj deo obrade teksta knjige se obi cno autoru ostavlja da u samom tekstu knjige na specijalan na cin ru cno ozna ci sve izraze koji se trebaju na ci u indeksu, pa se onda njihovo izdvajanje u programu svodi na prosto otkrivanje specijalne oznake uz vaniji izraz. Konstruisanje prazne mape koja predstavlja indeks na po cetku nije nita drugo nego konstruisanje objekta mape relativno sloenijeg tipa koji smo prethodno objasnili:
TreeMap<String,TreeSet<Integer>> indeks; indeks = new TreeMap<String,TreeSet<Integer>>();

Pretpostavimo da se zatim tokom skeniranja teksta knjige u programu na nekoj strani otkrije vaniji izraz koji treba dodati indeksu. Ako je izraz

348

11. G ENERI CKO PROGRAMIRANJE

predstavljen promenljivom izraz tipa String i broj strane promenljivom ref tipa int, onda metod kojim se u indeks dodaje izraz i jedna njegova referenca ima slede ci oblik:
public void dodajIndeksu(String izraz, int ref) { TreeSet<Integer> skupRef; // skup referenci za dati izraz

skupRef = indeks.get(izraz); if (skupRef == null) { // prva referenca za dati izraz TreeSet<Integer> prvaRef = new TreeSet<Integer>(); prvaRef.add(ref); indeks.put(izraz,prvaRef); } else skupRef.add(ref); }

U ovom metodu se najpre pozivom index.get(izraz) ispituje da li se u do sada formiranom indeksu nalazi dati izraz. Rezultat ovog poziva je vrednost null ili neprazan skup referenci koje su do tada pronadene za dati izraz. U prvom slu caju se dati izraz ne nalazi u indeksu, odnosno radi se o prvoj referenci za dati izraz, pa se dati izraz i novi skup od jedne reference dodaju u indeks. U drugom slu caju se dobija postoje ci skup referenci za dati izraz, pa se nova referenca samo dodaje tom skupu. Na kraju, nakon formiranja kompletnog indeksa, njegov sadraj treba prikazati (odtampati). Za taj zadatak treba azbu cnim redom prikazati svaki klju c i u nastavku njegov rezultuju ci skup referenci u rastu cem redosledu. Ovo se moe posti ci dvema ugnjedenim for-each petljama. U spoljanjoj petlji se redom prolazi kroz sve parove klju c/vrednost mape koja reprezentuje indeks i prikazuje klju c aktuelnog para. U unutranjoj petlji se za taj klju c prikazuje njegova vrednost, odnosno redom celi brojevi u skupu referenci u rastu cem redosledu. Metod u kojem se primenjuje ovaj postupak za prikazivanje indeksa ima slede ci oblik:
public void prikaiIndeks() { for (Map.Entry<String,TreeSet<Integer>> par : indeks.entrySet()) { String izraz = par.getKey(); TreeSet<Integer> skupRef = par.getValue(); System.out.print(izraz); // prikazivanje izraza for (int ref : skupRef) { // i njegove liste referenci System.out.print(" " + ref); }

11.3. Denisanje generi ckih klasa i metoda

349

System.out.println(); } }

Obratite panju u ovom metodu na tip parova mape koja reprezentuje indeks:
Map.Entry<String,TreeSet<Integer>>

Ovaj naizled prili cno komplikovan tip je zapravo lako protuma citi ukoliko se podsetimo da u mapi tipa Map<T,S> njeni parovi imaju tip Map.Entry<T,S>. Prema tome, parametri tipa u Map.Entry<String,TreeSet<Integer>> jednostavno su preuzeti iz deklaracije promenljive indeks.

11.3 Denisanje generi ckih klasa i metoda


Do sada smo upoznali kako se koriste postoje ce generi cke klase, interfejsi i metodi koji su deo Sistema kolekcija u Javi. Druga strana generi ckog programiranja je pisanje novih generi ckih klasa, interfejsa i metoda. Mada potreba za time dolazi do izraaja tek u sloenijim primenama pravljenja softverskih biblioteka, ovaj drugi aspekt generi ckog programiranja obezbeduje pravljenje vrlo optih programskih celina koje se mogu viekratno koristiti. Generi cka klasa je klasa sa jednim parametrom tipa, ili vie njih. Na primer, denicija generi cke klase za predstavljanje para koji sadri dva objekta istog tipa je:
public class Par<T> { private T prvi; private T drugi; // Konstruktori public Par() { this.prvi = null; this.drugi = null; } public Par(T prvi, T drugi) { this.prvi = prvi; this.drugi = drugi; } // Get i set metodi public T getPrvi() { return prvi; } public T getDrugi() {

350

11. G ENERI CKO PROGRAMIRANJE

return drugi; } public void setPrvi(T x) { prvi = x; } public void setDrugi(T x) { drugi = x; } }

Generi cka klasa Par iza svog imena sadri parametar tipa T u uglastim zagradama < >. Ovaj parametar tipa u deniciji klase koristi se na isti na cin kao i obi can tip: moe se upotrebiti za tipove polja i lokalnih promenljivih, kao i za tipove rezultata metoda. Generi cka klasa se u programu koristi navodenjem konkretnog tipa ume sto parametra tipa. Na primer, konkretna klasa Par<String> moe se smatrati obi cnom klasom koja ima dva privatna polja tipa String, zatim dva konstruktora Par<String>() i Par<String>(String,String), kao i metode:
String getPrvi() String getDrugi() void setPrvi(String) void setDrugi(String)

Na sli can na cin mogu se zamisliti i druge konkretne klase koje se dobijaju od generi cke klase Par<T>, recimo, Par<Double> ili Par<Color>. Moe se dakle smatrati, mada ne potpuno ta cno, da je generi cka klasa jedna matrica za pravljenje obi cnih klasa. Radi ilustracije kori cenja generi cke klase Par<T>, u nastavku je denisan i testiran metod kojim se istovremeno odreduje najmanji i najve ci element celobrojnog niza:
public class MinMax { public static void main(String[] args) { int[] a = {17, 5, 1, 4, 8, 23, 11, 5}; Par<Integer> mm = minmax(a); System.out.println("min = " + mm.getPrvi()); System.out.println("max = " + mm.getDrugi()); } public static Par<Integer> minmax(int[] a) { if (a == null || a.length == 0) return null;

11.3. Denisanje generi ckih klasa i metoda

351

int min = a[0], max = a[0]; for (int i = 1; i < a.length; i++) { if (min > a[i]) min = a[i]; if (max < a[i]) max = a[i]; } return new Par(new Integer(min),new Integer(max)); } }

Da bismo bolje razumeli primenu generi ckih klasa, razmotrimo strukturu podataka koja se naziva stek . Stek se sastoji od niza elemenata koji se zamilja da se nalazi u vertikalnom poloaju tako da su elementi steka predstavljeni kao da su naslagani jedan na drugi od dna ka vrhu. Najbolji intuitivni model steka kao strukture podataka je grupa naslaganih posluavnika u restoranu ili stog sena. Ono to karakterie strukturu podataka steka je isto ono to karakterie ove zi cke modele steka: samo vrh steka je (lako) pristupa can. Drugim re cima, novi element se steku dodaje samo na vrhu i iz steka se uklanja samo element koji se nalazi na vrhu. Operacija dodavanja novog elementa na vrh steka tradicionalno se naziva push, a operacija uklanjanja elementa s vrha steka naziva se pop. Stek na apstraktnom nivou moe sadrati elemente bilo kog tipa. Ako su to, recimo, celi brojevi, onda su izgled jednog takvog steka i osnovne operacije nad njim ilustrovani na slici 11.2.

69 8 105 10 17 po cetni stek 8 105 10 17 posle pop()

23 8 105 10 17 posle push(23)

Slika 11.2: Stek brojeva. Realizacija steka pomo cu povezane liste je o cigledno lako izvodljiva ukoliko se zamisli da vrh steka odgovara po cetku liste. Na primer, stek brojeva

352

11. G ENERI CKO PROGRAMIRANJE

denisan je slede com klasom u kojoj je dodat metod za proveru da li je stek prazan, jer uklanjanje elementa iz praznog steka nije ispravno:
public class StekBrojeva { private LinkedList<Integer> elementi = new LinkedList<Integer>(); public void push(Integer elem) { elementi.addFirst(item); } public Integer pop() { return elementi.removeFirst(); } public boolean isEmpty() { return (elementi.size() == 0); } }

Problem sa ovim pristupom je u tome to stek generalno moe sadrati elemente bilo kog tipa. Ako nam treba stek c iji su elementi stringovi, realni brojevi, gra cke komponente ili koji su bilo kog drugog tipa, onda bismo morali da piemo posebnu klasu za svaki tip elemenata steka. Pri tome, sve te klase bi bile skoro identi cne, osim to bi tip Integer elemenata steka brojeva bio zamenjen drugim tipom. Da bismo izbegli ovo ponavljanje programskog koda, i sve potencijalne greke koje to donosi, moemo denisati generi cku klasu Stek za predstavljanje steka bilo kog tipa elemenata. Na cin na koji se piu generi cke klase je jednostavan: konkretan tip Integer elemenata steka zamenjuje se parametrom tipa T i taj parametar tipa dodaje se imenu klase u zagradama <T>:
public class Stek<T> { private LinkedList<T> elementi = new LinkedList<T>(); public void push(T elem) { elementi.addFirst(item); } public T pop() { return elementi.removeFirst(); } public boolean isEmpty() { return (elementi.size() == 0); } }

11.3. Denisanje generi ckih klasa i metoda

353

Obratite panju na to da se parametar tipa T unutar generi cke klase Stek koristi potpuno isto kao obi cno ime tipa. Tako, ono se koristi kao tip rezultata metoda pop(), kao tip parametra elem metoda push() i c ak kao konkretan tip elemenata povezane liste u LinkedList<T>. Generi cka klasa Stek<T> se moe u primenama konkretizovati za predstavljanje steka c iji su elementi bilo kog tipa: Stek<Integer>, Stek<String>, Stek<JButton> i sli cno. Drugim re cima, generi cka klasa Stek koristi se na isti na cin kao postoje ce generi cke klase kao to su LinkedList i HashSet. Naredbom, na primer,
Stek<Double> s = new Stek<Double>();

konstruie se stek realnih brojeva i denie promenljiva s koja ukazuje na njega. Mada je ime parametra tipa T uobi cajeno u deniciji generi cke klase, ono je potpuno proizvoljno i moe se koristiti bilo koje ime. Ime parametra tipa u deniciji klase ima istu ulogu kao ime formalnog parametra u deniciji metoda, odnosno parametar tipa se zamenjuje konkretnim tipom kada se generi cka klasa koristi za denisanje promenljivih ili za konstruisanje objekata. Generi cki interfejsi u Javi deniu se na sli can na cin. Sli cno se deniu i generi cke klase i interfejsi koji imaju dva ili vie parametra tipa. Tipi can primer je denicija generi cke klase za predstavljanje para koji sadri dva objekta potencijalno razli citih tipova. Jednostavna verzija ove klase je:
public class Par<T,S> { private T prvi; private S drugi; // Konstruktor public Par(T prvi, S drugi) { this.prvi = prvi; this.drugi = drugi; } . . . }

Ova klasa se zatim moe koristiti za denisanje promenljivih i za konstruisanje objekata, na primer:
Par<String,Color> boja = new Par<String,Color>("Crvena", Color.RED); Par<Double,Double> ta cka = new Par<Double,Double>(1.7,23.69);

Obratite panju na to da imenu konstruktora klase Par<T,S> u njegovoj deniciji nisu dodati parametri tipova. To je zato to je formalno ime klase Par, a ne Par<T,S>. Opte pravilo je da se parametri tipova nikad ne dodaju imenima metoda ili konstruktora, nego samo imenima klasa i interfejsa.

354

11. G ENERI CKO PROGRAMIRANJE

Generi cki metodi


Pored generi ckih klasa i interfejsa, u Javi se mogu pisati i generi cki metodi. Da bismo pokazali kako se to radi, razmotrimo problem pretrage niza radi odredivanja da li se data vrednost nalazi u datom nizu. Kako tip elemenata niza i tip date vrednosti nisu bitni, osim to moraju biti jednaki, prirodno se name ce generi cko reenje:
public static boolean na di(T x, T[] a) { for (T elem : a) if (x.equals(elem)) return true; return false; } // skoro ispravno

U ovom metodu, tip elemenata niza i tip traene vrednosti ozna ceni su parametrom tipa T. Kako prilikom poziva ovog metoda svaki konkretni tip koji zamenjuje T sadri (nasledeni) metod equals(), generi cki metod na di() je logi cki ispravan. Medutim, ovako kako je napisan ne ce pro ci sintaksnu kontrolu, jer c e Java prevodilac podrazumevati da je T neki konkretan tip c ija denicija nije navedena. Zbog toga se mora na neki na cin ukazati da je T parametar tipa, a ne konkretan tip. Poto je to upravo svrha dodavanja oznake <T> iza imena klase u deniciji generi cke klase, sli cno se postupa i za generi cke metode. Kod generi ckih metoda, oznaka <T> navodi se u zaglavlju metoda iza svih modikatora i ispred tipa rezultata metoda:
public static <T> boolean na di(T x, T[] a) { for (T elem : a) if (x.equals(elem)) return true; return false; } // ispravno

Ovaj metod se moe koristiti za razli cite konkretne tipove nizova, na primer:
String[] jezici = {"Fotran", "Cobol", "Pascal", "C", "Ada"}; if (na di("Java",jezici)) . . .

Ili, ako je brojevi niz tipa Integer[], onda se pozivom


na di(17,brojevi)

odreduje da li se vrednost 17 nalazi medu elementima niza brojevi. U ovom slu caju se koristi autopakovanje, odnosno 17 se automatski konvertuje u vrednost tipa Integer, jer se u Javi generi cko programiranje primenjuje samo za objekte.

11.4. Ograni cenja za parametar tipa

355

Obratite panju na to da generi cki metodi mogu da se deniu kako u generi ckim klasama tako i u obi cnim klasama. Primetimo i da u prethodnim primerima nije naveden konkretni tip koji zamenjuje parametar tipa u pozivu metoda, jer prevodilac ima dovoljno informacija da sam o tome ispravno zaklju ci. U vrlo retkim situacija kada to nije mogu ce, konkretni tip se u pozivu metoda pie neposredno ispred imena metoda, na primer:
if (<String>na di("Java",jezici)) . . .

Metod na di() se moe koristiti za nizove bilo kog tipa. Ali vrlo sli can metod moe se napisati i za nalaenje objekta u bilo kojoj kolekciji:
public static <T> boolean na di(T x, Collection<T> k) { for (T elem : k) if (x.equals(elem)) return true; return false; }

Poto je Collection<T> generi cki tip, ovaj metod je vrlo generalan. Zaista, moe se koristiti za dinami cki niz tipa ArrayList c iji su elementi tipa Date, skup tipa TreeSet c iji su elementi tipa String, listu tipa LinkedList c iji su elementi tipa JButton i tako dalje.

11.4 Ograni cenja za parametar tipa


Parametar tipa u dosadanjim primerima generi ckog programiranja moe se zameniti bilo kojim konkretnim tipom. To sa jedne strane doprinosi optosti napisanih programskih celina, ali sa druge strane dovodi i do ograni cenja jer se mogu koristiti samo one mogu cnosti koje poseduju svi objekti. Na primer, s onim to znamo do sada ne moemo napisati generi cki metod u kojem se objekti uporeduju metodom compareTo(), jer taj metod nije denisan za objekte svih konkretnih klasa koje potencijalno mogu zameniti parametar tipa generi ckog metoda. Kod denisanja generi ckih klasa potrebno je dakle na neki na cin ograni citi mogu cnost zamene formalnog parametra tipa proizvoljnim aktuelnim tipom. Generi cko programiranje u Javi omogu cava postavljanje ograni cenja za parametre tipova na dva na cina. Jedan na cin slui za denisanje ograni cenih tipova, dok drugi uvodi takozvane doker-tipove.

356

11. G ENERI CKO PROGRAMIRANJE

Ograni ceni tipovi


Da bismo bolje razumeli ograni cene tipove, pokuajmo najpre da napiemo generi cki metod kojim se odreduje najmanji element niza:
public static <T> T min(T[] a) { // skoro ispravno if (a == null || a.length == 0) return null; T minElem = a[0]; for (int i = 1; i < a.length; i++) if (minElem.compareTo(a[i]) > 0) minElem = a[i]; return minElem; }

Problem s ovim generi ckim metodom je to to je u njemu promenljiva minElem tipa T, odnosno moe sadrati referencu na objekat bilo koje konkretne klase koja zamenjuje T u pozivu generi ckog metoda. Ali kako moemo znati da takva klasa sadri metod compareTo() kojim se minElem dalje uporeduje sa svakim elementom niza? Poto je metod compareTo() denisan u interfejsu Comparable, potrebno je dakle da se paramatar tipa ograni ci tako da se moe zameniti samo klasama koje implementiraju interfejs Comparable. Na cin na koji se ovo postie u Javi je navodenjem ograni cenja za paramatar tipa u zaglavlju metoda:
public static <T extends Comparable> T min(T[] a) . . .

Sada se generi cki metod min() moe pozivati samo za nizove c iji elementi 1 pripadaju klasama koje implementiraju interfejs Comparable. To su, recimo, klase Integer, String, Date i tako dalje, ali ne i, recimo, JButton. Obratite panju na to da se ovde za ograni cenje tipa koristi slubena re c extends, a ne implements, iako se radi o interfejsu Comparable. To je opte pravilo, jer se time izraava uslov da parametar tipa treba da bude podtip nekog tipa. Ova vrsta ograni cenja za parametar tipa naziva se ograni ceni tip. Ograni ceni tipovi se mogu koristiti umesto formalnog parametra tipa ne samo za denisanje generi ckih metoda kao u prethodnom primeru, nego i za denisanje generi ckih klasa ili interfejsa. Radi ilustracije, uzmimo da grupu gra ckih komponenti istog tipa u programu treba predstaviti jednom generi ckom klasom GrupaKomponenti. Tako bi, recimo, klasa GrupaKomponenti<JButton> predstavljala grupu dugmadi, dok bi klasa GrupaKomponenti<JPanel> predstavljala grupu panela. Generi cka klasa treba dodatno da sadri metode kojima bi se odredene operacije
1 Sliku dodatno komplikuje c injenica da je Comparable i sam jedan generi cki interfejs, ali to je za sada manje vano.

11.4. Ograni cenja za parametar tipa

357

primenljivale na sve komponente u grupi. Na primer, metod za iscrtavanje svih komponenti mogao bi imati ovaj oblik:
public void nacrtajSve() { . . // Pozivanje metoda repaint() . // za svaku komponentu u grupi . }

Denicija parametrizovane klase u obliku GrupaKomponenti<T> nije dobra zbog sli cnog problema koji smo sreli u prvoj verziji metoda min() u prethodnom primeru. Naime, metod repaint() je denisan za sve objekte tipa JComponent, ali ne i za objekte proizvoljnog tipa. Nema dakle smisla dozvoliti klase kao to su GrupaKomponenti<String> ili GrupaKomponenti<Integer>, jer klase String i Integer nemaju metod repaint(). Potrebno je, drugim re cima, ograni citi formalni parametar tipa u deniciji parametrizovane klase GrupaKomponenti<T> tako da se T moe zameniti samo klasom JComponent ili nekom njenom naslednicom. Reenje se sastoji u pisanju ograni cenog tipa T extends JComponent umesto prostog tipa T u deniciji klase:
public class GrupaKomponenti<T extends JComponent> { private ArrayList<T> komponente; // cuvanje komponenti u grupi public void nacrtajSve() { for (JComponent k : komponente) if (k != null) k.repaint(); } // Dodavanje nove komponente u grupu public void dodaj(T k) { komponente.add(k); } . . // Ostali metodi . }

Zapis T extends OsnovniTip u optem slu caju ozna cava neki tip T koji je jednak tipu OsnovniTip ili je podtip od tipa OsnovniTip. Posledica toga je da svaki objekat tipa T jesto ujedno i objekat tipa OsnovniTip, pa se sve operacije koje su denisane za objekte tipa OsnovniTip mogu primeniti i na

358

11. G ENERI CKO PROGRAMIRANJE

objekte tipa T. Tip OsnovniTip ne mora biti ime klase, ve c moe biti sve to predstavlja neki stvarni tip. To moe biti i, recimo, neki interfejs ili c ak neki parametrizovani tip.

Doker-tipovi
Druga vrsta ograni cenja za parametre tipova kod generi ckog programiranja u Javi jesu takozvani doker-tipovi. Doker-tip se ne moe koristiti kao formalni parametar tipa klase ili interfejsa, ali se moe koristiti za tipove promenljivih ili, mnogo c e ce, za parametre u listi parametara generi ckih metoda. Pre nego to moemo razumeti doker-tipove, moramo poznavati jo jedan detalj u vezi sa nasledivanjem za generi cke tipove. Posmatrajmo jednu klasu i neku njenu naslednicu radi odredenosti, uzmimo klasu Slubenik i njenu podklasu ef. U kontekstu generi ckih tipova moe se postaviti pitanje da li se ovaj njihov odnos prenosi na konkretne klase koje se dobijaju zamenom parametrizovanih tipova? Na primer, da li je Par<ef> podklasa od Par<Slubenik>? Odgovor je negativan, iako se to moda kosi s naom intuicijom. Naime, opte pravilo kod generi ckih klasa je da ne postoji nikakva veza izmedu GenKlasa<A> i GenKlasa<B> bez obzira na to u kakvoj su vezi konkretne klase A i B.2 Ovo pravilo u nekim slu cajevima ima negativan efekat na prednosti generi ckog programiranja. Na primer, metod za prikazivanje para slubenika moe se napisati otprilike na ovaj na cin:
public static void prikaiKolege(Par<Slubenik> p) { Slubenik prvi = p.getPrvi(); Slubenik drugi = p.getDrugi(); . . // Prikazivanje imena prvog i drugog slubenika . }

Ali ovaj metod se ne moe koristiti za prikazivanje para efova, iako su efovi takode slubenici:
Par<ef> dvaefa = new Par<ef>(direktor,na celnik); prikaiKolege(dvaefa); // GREKA!

2 Ovo pravilo je neophodno zbog konzistentnosti sistema tipova u Javi.

11.4. Ograni cenja za parametar tipa

359

Naime, konkretne klase Par<Slubenik> i Par<ef> ne stoje ni u kakvoj medusobnoj vezi, iako su instance iste generi cke klase Par<T>, a klasa ef je pod klasa od Slubenik. Zato se promenljiva dvaefa tipa Par<ef> ne moe koristiti kao argument u pozivu metoda prikaiKolege() c iji je parametar tipa Par<Slubenik>. Reenje za ovo prili cno neprirodno ograni cenje su dokertipovi:
public static void prikaiKolege(Par<? extends Slubenik> p) . . .

Zapis ? extends Slubenik u tipu parametra ovog metoda ozna cava svaki tip koji je jednak klasi Slubenik ili je podklasa od klase Slubenik. Zbog toga doker-tip
Par<? extends Slubenik>

obuhvata klase Par<Slubenik> i Par<ef>, ali ne i recimo Par<String>. To zna ci da, poto je parametar metoda prikaiKolege() upravo ovog dokertipa, argument u pozivu metoda prikaiKolege() moe biti bilo kojeg od dva tipa Par<Slubenik> ili Par<ef>. Kao drugi primer, razmotrimo pitanje dodavanja steku svih objekata koji se nalaze u datoj kolekciji. Preciznije, ako je s stek tipa Stek<T> i k kolekcija tipa Collection<T>, onda metodom s.addAll(k) treba dodati sve objekte iz k na s. Objekti kolekcije k su istog tipa T kao objekti steka, mada ako bolje razmislimo mogu biti optiji. Naime, ako je S podklasa od T, onda kolekcija k moe biti tipa Collection<S>. To ima smisla, jer je svaki objekat tipa S automatski i tipa T i moe se zato dodati steku s. Pove canje optosti metoda addAll() moe se posti ci primenom dokertipa za parametar tog metoda. Nova verzija generi cke klase Stek koja se denisana ranije u ovom odeljku ima ovaj oblik:
public class Stek<T> { private LinkedList<T> elementi = new LinkedList<T>(); public void push(T elem) { elementi.addFirst(item); } public T pop() { return elementi.removeFirst(); } public boolean isEmpty() { return (elementi.size() == 0); } public void addAll(Collection<? extends T> k) {

360

11. G ENERI CKO PROGRAMIRANJE

// Dodavanje svih elemenata kolekcije na vrh steka for (T elem : k) push(elem); } }

Obratite panju na to da se ovde doker-tipovi kombinuju sa generi ckim klasama. U prethodnoj deniciji generi cke klase Stek<T>, parametar T ima ulogu speci cnog, mada nepoznatog imena tipa. Unutar te klase doker-tip ? extends T ozna cava naravno T ili neki izvedeni tip od T. Kada se konstruie konkretni stek tipa recimo Stek<Slubenik>, onda se tip T zamenjuje sa Slubenik i doker-tip ? extends T u deniciji metoda addAll() postaje ? extends Slubenik. To zna ci da se ovaj metod moe primeniti kako za kolekciju objekata tipa Slubenik, tako i za kolekciju objekata tipa ef. U deniciji metoda addAll() se koristi for-each petlja za postupno dodavanje elemenata kolekcije na vrh steka. Mogu cu sumnju u ispravnost ove petlje izaziva to to se pojedina cnim elementima kolekcije redom pristupa preko promenljive elem tipa T, a elementi kolekcije mogu tipa S, gde je S podklasa od T. Ali to prema principu podtipa u Javi nije greka, jer se objekti tipa S mogu dodeliti promenljivoj tipa T ukoliko je S podklasa od T. U zapisu oblika ? extends T umesto T moe stajati interfejs, a ne iskljuc ivo klasa. Primetimo da se koristi re c extends, a ne implements, c ak i ukoliko je T interfejs. Radi ilustracije, podsetimo se da je Runnable interfejs koji predstavlja zadatak koji se moe izvravati unutar posebne niti paralelno s drugim zadacima. Jedan generi cki metod za paralelno izvravanje svih zadataka tipa Runnable u datoj kolekciji je:
public void izvriParalelno(Collection<? extends Runnable> zadaci) { for (Runnable z : zadaci) { Thread nit = new Thread(z); // posebna nit za izvravanje jednog // zadatka, tj. metoda z.run() nit.start(); } }

Metod addAll() u generi ckoj klasi Stek<T> dodaje sve objekte iz neke kolekcije na stek. Pretpostavimo da je potrebno napisati metod kojim se obavlja suprotna operacija: sve objekte iz steka treba dodati datoj kolekciji. Reenje koje prvo pada na pamet je metod c ije je zaglavlje:
public void addAllTo(Collection<T> k)

Ovaj metod medutim nije dovoljno generalan, jer se moe primeniti samo za kolekcije c iji su elementi istog tipa T kao i elementi steka. Optije reenje

11.4. Ograni cenja za parametar tipa

361

bi bio metod kojim se elementi steka mogu dodati kolekciji c iji su elementi nekog tipa S koji je nadtip od T. Ovaj odnos tipova izraava se drugom vrstom doker-tipa ? super T koja ozna cava bilo T ili neku nadklasu od T. Na primer,
Collection<? super ef>

obuhvata tipove Collection<ef> i Collection<Slubenik>. Koriste ci ovu vrstu doker-tipa za parametar metoda addAllTo(), kompletna generi cka klasa za strukturu podataka steka ima ovaj oblik:
public class Stek<T> { private LinkedList<T> elementi = new LinkedList<T>(); public void push(T elem) { elementi.addFirst(item); } public T pop() { return elementi.removeFirst(); } public boolean isEmpty() { return (elementi.size() == 0); } public void addAll(Collection<? extends T> k) { // Dodavanje svih elemenata kolekcije na vrh steka for (T elem : k) push(elem); } public void addAllTo(Collection<? super T> k) { // Uklanjanje elemenata sa steka i dodavanje kolekciji while (!isEmpty()) { T elem = pop(); k.add(elem); } } }

Napomenimo jo da se moe koristiti i tre ca vrsta doker-tipa <?> koja ozna cava nepoznat tip i u sutini zamenjuje zapis <? extends Object>. Na primer, generi cki metod za prikazivanje bilo koje kolekcije objekata ne bi bio dobro napisan u ovom obliku:
public void prikaiKolekciju(Collection<Object> k) { for (Object elem : k)

362

11. G ENERI CKO PROGRAMIRANJE

System.out.println(elem); }

Problem je to to tip parametra Collection<Object> ovog metoda nije ni u kakvoj vezi sa, recimo, tipom Collection<Integer>. To zna ci da se ovaj metod ne moe koristiti za prikazivanje kolekcije celih brojeva, a ni za prikazivanje kolekcije objekata bilo kog drugog tipa razli citog od ba tipa Object. Mnogo bolje reenje se dobija primenom neograni cenog doker-tipa:
public void prikaiKolekciju(Collection<?> k) { for (Object elem : k) System.out.println(elem); }

Zaklju cimo na kraju da se ograni ceni tipovi i doker-tipovi, iako su povezani u odredenom smislu, koriste na vrlo razli cite na cine. Ograni ceni tip se moe koristiti samo kao formalni parametar tipa u deniciji generi ckog metoda, klase ili interfejsa. Doker-tip se naj ce ce koristi za odredivanje tipova formalnih parametara u deniciji generi ckih metoda i ne moe se koristiti kao formalni parametar tipa. Jo jedna sintaksna razlika je to to se kod ograni cenih tipova, za razliku od doker-tipova, uvek koristi re c extends, nikad super.

Primer: generi cko sortiranje liste


Na strani 335 je pokazan jedan ekasan postupak (merge sort ) za sortiranje liste elemenata. U stvari, elementi liste su bili celobrojnog tipa, mada paljivija analiza tog postupka otkriva da se od celobrojnog tipa jedino koristi mogu cnost poredenja vrednosti celobrojnog tipa. Elementi liste koja se sor tira mogu zato biti i realni brojevi i stringovi i, najoptije, bilo koji objekti za koje je denisan metod compareTo(). Drugim re cima, elementi liste mogu biti objekti bilo koje klase koja implementira interfejs Comparable. Opti metod za sortiranje liste treba dakle samo da sprovede ovo ograni cenje za tip elemenata liste, a sve ostale izmene se prakti cno sastoje od pisanja parametra tipa umesto konkretnog tipa Integer. Ali ukoliko ovo pokuamo da reimo koriste ci doker-tip za elemente liste, dobijamo generi cki metod c iji je po cetak:
public static void mergeSort(List<? extends Comparable> l) { if (l.size() > 1) { // lista l ima bar dva elementa

// 1. Podeliti listu l u dve polovine l1 i l2 List<###> l1 = new LinkedList<###>(); // tip elemenata liste l1? List<###> l2 = new LinkedList<###>(); // tip elemenata liste l2?

11.4. Ograni cenja za parametar tipa

363

. . . } }

Ovo pokazuje da smo na pogrenom putu, jer nemamo konkretno ime za nepoznat tip elemenata liste ozna cen zapisom ? extends Comparable. A ime tog tipa je neophodno radi konstruisanja listi l1 i l2 c iji elementi moraju biti istog tipa kao i elementi liste koja se sortira. U ovom slu caju dakle moramo koristiti ograni cen tip za elemente liste, jer se onda dobija konkretno ime nepoznatog tipa koje se moe koristiti u telu metoda. Ovo reenje je primenjeno u generi ckom metodu za sortiranje liste (merge sort ) koji je u nastavku prikazan u celosti.
public static <T extends Comparable<T>> void mergeSort(List<T> l) { if (l.size() > 1) { // lista l ima bar dva elementa

// Podeliti listu l u dve polovine l1 i l2 List<T> l1 = new LinkedList<T>(); List<T> l2 = new LinkedList<T>(); ListIterator<T> it = l.listIterator(); boolean dodajL1 = true; // element liste l ide u l1 ili l2 while (it.hasNext()) { T e = it.next(); if (dodajL1) l1.add(e); else l2.add(e); dodajL1 = !dodajL1; } // Rekurzivno sortirati liste l1 i l2 mergeSort(l1); mergeSort(l2); // Objediniti sortirane liste l1 i l2 u sortiranu listu l l.clear(); ListIterator<T> it1 = l1.listIterator(); ListIterator<T> it2 = l2.listIterator(); T e1 = it1.next(); T e2 = it2.next(); while (true) { // l1 ima bar jedan element // l2 ima bar jedan element

364

11. G ENERI CKO PROGRAMIRANJE

if (e1.compareTo(e2) <= 0) { l.add(e1); if (it1.hasNext()) e1 = it1.next(); else { l.add(e2); while (it2.hasNext()) l.add(it2.next()); break; } } else { l.add(e2); if (it2.hasNext()) e2 = it2.next(); else { l.add(e1); while (it1.hasNext()) l.add(it1.next()); break; } } } } }

Obratite panju na to da je parametrizovan ograni cen tip prethodnog generi ckog metoda naveden u obliku T extends Comparable<T>, a ne u prostom obliku T extends Comparable. Razlog je to to objekti liste tipa T treba zapravo da implementiraju parametrizovan interfejs Comparable<T>. Primetimo i da je ovo primer zapisa T extends OsnovniTip za ograni ceni tip u kojem je OsnovniTip jedan parametrizovan tip, a ne obi cna klasa.

L ITERATURA

[1] [2] [3] [4] [5] [6] [7] [8] [9]

Ken Arnold, James Gosling, and David Holmes. The Java Programming Language. Prentice Hall, fourth edition, 2006. Harvey Deitel and Paul Deitel. Java How to Program. Prentice Hall, seventh edition, 2007. David J. Eck. Introduction to Programming Using Java. Free version at http://math.hws.edu/javanotes, fth edition, 2006. David Flanagan. Java in a Nutshell. OReilly, fth edition, 2005. Mark Guzdial and Barbara Ericson. Introduction to Computing and Programming in Java: A Multimedia Approach. Prentice Hall, 2007. Cay S. Horstmann and Gary Cornell. Core Java, Volume IFundamentals. Prentice Hall PTR, eighth edition, 2007. Cay S. Horstmann and Gary Cornell. Core Java, Volume IIAdvanced Features. Prentice Hall PTR, eighth edition, 2008. Ivor Horton. Beginning Java 2. Wiley Publishing, JDK 5 edition, 2005. Jonathan Knudsen and Patrick Niemeyer. Learning Java. OReilly, third edition, 2005.

[10] Daniel Liang. Introduction to Java Programming, Comprehensive Version. Prentice Hall, seventh edition, 2008. [11] Richard F. Raposa. Java in 60 Minutes a Day. Wiley Publishing, 2003. [12] Matthew Robinson and Pavel Vorobiev. Java Swing. Manning, second edition, 2003. [13] Herbert Schildt. Java Programming Cookbook. McGraw-Hill, 2007. [14] Dejan ivkovi c. Osnove Java programiranja. Univerzitet Singidunum, 2009. [15] Dejan ivkovi c. Osnove Java programiranja Zbirka pitanja i zadataka sa reenjima. Univerzitet Singidunum, 2010.

I NDEKS

A
ActionEvent, 194 ActionListener, 196

continue naredba, 28

curenje memorije, 69, 209

adapterske klase, 202 adresa petlje, 290 localhost, 290 aktuelni (radni) direktorijum, 246 anonimne klase, 159 aplet, 191 apstraktne klase, 144 apstraktni metodi, 147 argumenti metoda, 31 ArrayList, 104 ArrayList<T>, 107, 333 Arrays, 87 asinhrona komunikacija programa, 311 asocijativnost operatora, 12

D doker-tipovi, 358
DataInputStream, 235 DataOutputStream, 235

datoteka, 241 File, 246 ime, 246


JFileChooser, 250

B beskona cna petlja, 24 biblioteka AWT, 164 biblioteka Swing, 164 binarni U/I tokovi, 231 InputStream, 233 OutputStream, 233 blok naredbi, 16 boje, 186 break naredba, 28

sadraj, 246 denicija metoda, 31 denisanje gra ckih komponenti, 181 denisanje klasa izuzetaka, 225 denisanje metoda specikatori pristupa, 33 dinami cki nizovi, 101, 104 dinami cko (kasno) vezivanje, 135 direktorijum, 241 aktuelni (radni), 246 DNS server, 290 do-while naredba, 25 dokumentacija Jave, 49 domensko ime, 290 dvodimenzionalni nizovi, 93

C
Collection<T>, 323, 324 List<T>, 324 Set<T>, 324 Color, 187 Comparable, 149 Comparable<T>, 327 Comparator<T>, 338 Component, 170 Container, 170

E eksplicitna konverzija tipa, 14 enkapsulacija (u caurivanje), 70 EventObject, 194

F
File, 246, 248 FileInputStream, 242 FileOutputStream, 242 final

za klase, 128

368

I NDEKS

za konstante, 10 za metode, 128 finaly klauzula, 216 folder, 241 fontovi, 189 Font, 189 for naredba, 27

G generi cka klasa, 349 parametar tipa, 349 generi cki metod, 354 parametar tipa, 354 generi cko programiranje, 321 geteri, 71 gra cki elementi, 166 dijalozi, 167 komponente, 167 kontejneri, 167 okviri, 167 prozori, 167 gra cki korisni cki interfejs, 163 gra cki programi, 163 Graphics, 181 Graphics2D, 184

H
HashMap<T,S>, 341 HashSet<T>, 337

osobine, 151 Internet Protocol (IP), 289 IP adresa, 290 IP adresa, 290 adresa petlje, 290 domensko ime, 290 DNS server, 290 iterator, 329 Iterator<T>, 329 izraz, 10 konverzija tipa, 12 operatori, 11 asocijativnost, 12 prioritet, 11 izuzetak, 211 denisanje klase, 225 klauzula throws, 223 mehanizam postupanja, 220 naredba throw, 218 neproveravani, 222 proveravani, 222 rukovalac, 215 rukovanje, 211 hvatanje i obradivanje, 211 obavezno i neobavezno, 222 Throwable, 212 try-catch naredba, 213 izvor dogadaja, 194

he tabela, 339 hijerarhija klasa, 118 hip memorija, 58

I ime datoteke, 246 apsolutno, 247 File, 246


JFileChooser, 250

relativno, 247 indeks knjige, 346 InputStream, 233


DataInputStream, 235 FileInputStream, 242 ObjectInputStream, 240 InputStreamReader, 235

interfejs, 149 implementacija, 149

J Java bajtkod, 2 Java virtuelna maina, 2 JButton, 171 JCheckBox, 171 JComboBox, 171 JComponent, 171 JEditorPane, 294 JFileChooser, 250 JFrame, 167 nasledeni metodi, 169 JLabel, 171 JOptionPane, 165, 205 JPanel, 171 JProgressBar, 280 JRadioButton, 171 JTextField, 171

Indeks

369

K katanac, 277 klasa c lanovi, 43, 54 objektni (nestati cki, instancni), 54 stati cki (klasni), 54 adapterska, 202 anonimna, 159 apstraktna, 144 generi cka, 349 omota c primitivnog tipa, 328 polja, 42 klase za crtanje, 184 klasni dijagram, 119 klijent, 298 klijent-server programiranje, 297 klijentski soket, 298 kolekcija, 323 iterator, 329 jednakost i poredenje objekata, 326 lista, 323 pogled na, 343 skup, 323 komentari, 4 dokumentacioni, 5 kompajleri, 2 komponente, 170 raspored unutar kontejnera, 174 konkretne klase, 147 konstante, 10 konstruktor, 64 u klasama naslednicama, 129 kontejneri, 170 raspored komponenti, 174 konverzija tipa, 12 kooperacija niti, 284 koordinate gra ckih elemenata, 173 kriti cna oblast, 276

lokalne klase, 158

M
Map.Entry<T,S>, 344 Map<T,S>, 323, 340, 341 HashMap<T,S>, 341 Map.Entry<T,S>, 344 TreeMap<T,S>, 341

L
LayoutManager, 174 LinkedList<T>, 333 List<T>, 324

mapa, 339 pogled na, 343 merge sort, 335, 363 metod, 30 apstraktni, 147 argumenti, 31 denicija, 31 klauzula throws, 223 generi cki, 354 geter, 71 konstruktor, 64 nadja cavanje, 126 parametri, 31 potpis, 38 pozivanje, 31 preoptere ceni, 38 promenljiv broj argumenata, 91 rekurzivni, 39 rezultat, 31 seter (mutator), 71 sinhronizovan, 274 vra canje rezultata, 36 mime format, 293 MouseAdapter, 203 MouseEvent, 194 MouseListener, 197, 202 mreno programiranje, 289 klijent-server, 291, 297 port, 298 soket, 291 veb programiranje, 291 vienitno, 307 mrtva petlja, 277 multitasking, 259 multithreading, 259

lista, 323, 331


ArrayList<T>, 333 LinkedList<T>, 333 List<T>, 324

localhost, 290

N nabrojivi tipovi, 140 nadja cavanje metoda, 126

370

I NDEKS

naredba deklaracije promenljive, 15, 16 naredba dodele, 15 naredba povratka, 36 naredbe grananja if-else naredba, 18 switch naredba, 22 ugnjedavanje, 19 naredbe ponavljanja do-while naredba, 25 for-each petlja, 90 for naredba, 27 kontrolna promenljiva (broja c), 28 while naredba, 23 nasledivanje klasa, 113 neproveravani izuzeci, 222 nit, Vidi nit izvravanja nit izvravanja, 259 JProgressBar, 280 kooperacija, 284 multithreading, 259 sinhronizacija, 271 Thread, 260 za gra cke dogadaje, 279 zadatak, 259 Runnable, 260 nizovi, 77 bazni tip, 77 dinami cki, 101, 104 duina, 77 dvodimenzionalni, 93 matrica (tabela), 93 elementi, 77 indeks, 77 inicijalizacija, 80 jednodimenzionalni, 77 length, 80 viedimenzionalni, 93

Object, 130 objektne ugnjedene klase, 156 objektno orijentisano programiranje, 53 oblast vaenja imena, 46 ograni ceni tipovi, 356 okvir, 167 operator new, 58, 66 operatori, 11 OutputStream, 233 DataOutputStream, 235 FileOutputStream, 242 ObjectOutputStream, 240 OutputStreamWriter, 235 ozna cena petlja, 29

O
Object, 130 ObjectInputStream, 240 ObjectOutputStream, 240

objekat instanca klase, 54 jednakost i poredenje, 326 Comparable<T>, 327 Comparator<T>, 338 konstrukcija i inicijalizacija, 62

P paket, 49, 290 import, 49 package, 50 anonimni, 50 kori cenje, 49 parametar tipa, 349, 354 ograni cenja, 355 doker-tipovi, 358 ograni ceni tipovi, 356 parametri metoda, 31 parametrizovani tipovi, 107, 321 sistem kolekcija, 322 petlja, 23 beskona cna, 24 ozna cena, 29 telo petlje, 23 ugnjedavanje, 25 uslov nastavka (ili prekida), 23 piksel, 168, 173 podlista, 345 podskup, 345 pogledi na mape i kolekcije, 342 Map.Entry<T,S>, 344 podlista, 345 podskup, 345 Point2D, 186 polimorzam, 134 polja, 16, 42 pop, 351 port, 298 potpis metoda, 38 pozivanje metoda, 31, 34

Indeks

371

prenoenje argumenata po vrednosti, 35 prazna naredba, 21 prekora cenje bafera, 208 preoptere ceni metodi, 38, 134 prevodioci, 2 princip podtipa, 106, 122 PrintWriter, 236 prioritet operatora, 11 program asinhrona komunikacija, 311 gra cki, 163 greke, 207 ispravan i robustan, 207 klijent, 298 server, 298 slobodan format, 5 ulaz i izlaz, 229 hijerarhija klasa, 233 voden 163 dogadajima, programiranje generi cko, 321 mreno, 289 objektno orijentisano, 53 programske greke, 207 curenje memorije, 209 izuzeci, 211 kori cenje pokaziva ca null, 209 nedeklarisane promenljive, 208 prekora cenje bafera, 208 promenljiva, 8 alociranje, 45 dealociranje, 45 duina trajanja, 44 globalna, 43 zaklonjena, 46 lokalna, 16, 17, 43 oblast vaenja, 17 protokol, 289 Internet Protocol (IP), 289 paketi, 290 Transmission Control Protocol (TCP), 290 User Datagram Protocol (UDP), 290 proveravani izuzeci, 222 push, 351

R raspored komponenti, 174 BorderLayout, 178 FlowLayout, 175 GridLayout, 176 postupak za razmetanje, 174 Reader, 233 InputStreamReader, 235 Rectangle2D, 185 referenca (pokaziva c), 58 rekurzivni metodi, 39 bazni slu caj, 41 return naredba, 36 rezultat metoda, 31 rukovalac dogadaja, 164, 195 rukovalac izuzetka, 215 rukovanje dogadajima, 194 Runnable, 260

S sakupljanje otpadaka, 69 Scanner, 237 server, 298 vienitni, 307 serverski soket, 298 ServerSocket, 299 Set<T>, 324, 337 seteri (mutatori), 71 sinhronizacija niti, 271 greka trke niti, 272 katanac, 277 mrtva petlja, 277 uzajamna isklju civost, 274 sinhronizovani metodi i naredbe, 274 sistem kolekcija, 322 Collection<T>, 323 kolekcije i mape, 323 Map<T,S>, 323 skup, 323, 337 HashSet<T>, 337 Set<T>, 337 TreeSet<T>, 337 slubene (rezervisane) re ci, 9 Socket, 299 soket, 291 klijentski, 298 serverski, 298 ServerSocket, 299

372

I NDEKS

Socket, 299 sortiranje objedinjavanjem, 335 standardne komponente, 171 JButton, 171 JCheckBox, 171 JComboBox, 171 JLabel, 171 JRadioButton, 171 JTextField, 171 stanje trke, 272 stati cke ugnjedene klase, 155 stati cko (rano) vezivanje, 134 stek, 351 pop, 351 push, 351 strukture podataka, 77 stek, 351 super, 125 poziv konstruktora nasledene klase, 129 pristup zaklonjenim c lanovima nasledene klase, 126

binarni i tekstualni, 231 izlazni, 230 ulazni, 231 Transmission Control Protocol (TCP), 290 TreeMap<T,S>, 341 TreeSet<T>, 337 try-catch naredba, 213 klauzula finaly, 216

U ugnjedene klase, 154 lokalne i anonimne, 155 stati cke i objektne, 154 uklanjanje objekata, 68 Unicode, 7 univerzalni lokator resursa, 291 URL, 291 User Datagram Protocol (UDP), 290 uslov nastavka (ili prekida), 23 uzajamna isklju civost, 274

T tekstualni U/I tokovi, 231 Reader, 233 Writer, 233 telo petlje, 23 this, 73 implicitni argument metoda, 74 poziv preoptere cenog konstruktora,75 Thread, 260, 264 throw naredba, 218 Throwable, 212 Error, 212 Exception, 212 throws klauzula, 223 tip podataka, 6 klasni, 7 omota ci primitivnih tipova, 328 parametrizovani, 107, 321 primitivni, 6 void, 8 celobrojni, 7 logi cki, 7 realni, 7 znakovni, 7 tok podataka, 230

V veb programiranje, 291 viedimenzionalni nizovi, 93 viekratna upotreba, 67 viestruko nasledivanje, 149 vise ci pokaziva ci, 69

W
while naredba, 23 WindowAdapter, 203 WindowListener, 202 Writer, 233 OutputStreamWriter, 235 PrintWriter, 236

You might also like