Download as ppt, pdf, or txt
Download as ppt, pdf, or txt
You are on page 1of 86

Objektno orijentisano programiranje

Java, generički tipovi


Izuzeci, nastavak
• Većina predefinisanih klasa izuzetaka u Javi ne dodaje
dalje informacije o uslovima koji su kreirali izuzetak
• Ovaj generalni nedostatak je iz razloga što te informacije
u većini slučajeva mogu da se prikupe jedino
predznanjem o izračunavanjima koja su vršena kada se
desio izuzetak, a jedini ko zna o tome je onaj ko piše
program
• Ako nam je potrebno više informacija o okolnostima pod
kojima se desio izuzetak, moraćemo sami da ih
obezbedimo, tj. da definišemo sopstvene izuzetke
Definisanje naših izuzetaka
• Postoje 2 osnovna razloga za definisanje naših klasa izuzetaka:
želimo da dodamo informacije kada se desi standardni
izuzetak, i možemo to učiniti reizbacivanjem objekta naše
klase izuzetaka
možemo imati greške koje nastaju u našem kodu koje
opravdavaju kreiranje specijalne klase izuzetaka
• Definisanje naše klase izuzetaka
– naše klase izuzetaka moraju uvek imati Throwable za
superklasu. Najbolje je izvoditi ih iz klase Exception, mada se
mogu izvesti iz proizvoljne standardne klase izuzetaka. To će
dopustiti kompajleru da prati gde u našem programu je
izbačen izuzetak i da proverava da li je izuzetak uhvaćen ili
deklarisan kao izbačen u metodu. Ako kao superklasu
koristimo RuntimeException ili neku od njenih potklasa,
provera kompajlera biće ukinuta
Definisanje naše klase izuzetaka
primer:
public class GrozanProblemException extends Exception {
// Konstruktori
public GrozanProblemException(){}

public GrozanProblemException(String s){


super(s); // poziv konstruktora bazne klase
}
}
Definisanje naše klase izuzetaka
• Ovo je minimum koji treba da obezbedimo u definiciji naše klase
izuzetaka ( podrazumevani konstruktor i konstruktor koji prihvata
String-argument )
• Poruka smeštena u superklasi Exception ( zapravo Throwable) će
automatski biti inicijalizovana imenom naše klase, bez obzira na to
koji konstruktor naše klase se koristi. String prosleđen drugom
konstruktoru biće dopisan na ime klase da bi se formirala poruka
koja se čuva u objektu izuzetku
• Naravno, mogu se dodati i drugi konstruktori. Generalno, želećemo
to, posebno ako reizbacujemo naš izuzetak nakon izbacivanja
standardnog izuzetka.
• Dodatno, tipično ćemo želeti da dodamo instancne promenljive koje
će čuvati dodatne informacije o problemu, plus metode koji će
omogućiti kodu u catch bloku da pristupa tim podacima.
• Pošto je naša klasa obavezno izvedena iz klase Throwable,
informacije steka izvršavanja će biti automatski dostupne našim
izuzecima
Izbacivanje našeg izuzetka
• throw-naredbom, kao što je ranije rečeno, npr.
GrozanProblemException e = new GrozanProblemException();
throw e;
• Metod će prekinuti izvršavanje u ovoj tački – osim ako je gornji
isečak koda u try ili catch bloku, sa pridruženom finally klauzom čiji
će se sadržaj izvršiti pre kraja metoda. Izuzetak će biti izbačen u
pozivajući program u tački gde je pozvan metod. Poruka u objektu
izuzetka će se sastojati samo od kvalifikovanog imena klase
izuzetaka.
• Ako želimo da dodamo specifičnu poruku izuzetku:
GrozanProblemException e = new GrozanProblemException("Uh,
nevolja!");
• Ovde smo koristili drugi konstruktor
Izbacivanje našeg izuzetka
• getMessage() metod nasleđen od Throwable će vratiti String objekat
koji sadrži sledeći string:
"GrozanProblemException: Uh, nevolja!"
• može i ovako, u jednoj naredbi:
throw new GrozanProblemException("Strasne poteskoce!");
Strategija rukovanjem izuzecima
• Potrebno je promisliti šta želimo da postignemo rukovanjem
izuzecima u našem programu. Ne postoje čvrsta pravila. U nekim
situacijama možemo popraviti problem i omogućiti da program
nastavi da se izvršava kao da se ništa nije desilo. U drugim
situacijama, ispis steka izvršavanja i brzi završetak programa
predstavljaju najbolji pristup – brzi završetak programa se postiže
pozivom metoda exit() klase System.
Generički klasni tipovi
• Šta su generički tipovi?
• Generički tip, koji se takođe naziva i parametrizovani tip, je definicija
klase ili interfejsa koja ima jedan ili više parametara.
• Stvarni klasni ili interfejsni tip se dobija od generičkog
prosleđivanjem argumenta tipa za svaki od parametara koji se
pojavljuju u generičkom tipu
Definisanje generičkog klasnog tipa
• Na primeru povezane liste
• Definicija generičkog klasnog tipa izgleda vrlo slično definiciji obične
klase, ali uz zadavanje parametara nakon imena klase. Npr.
public class PovezanaLista<T>{
// Definicija genericke klase …
}
• Parametar koji se pojavljuje između <> naziva se tipski parametar.
Ime, T, identifikuje tipski parametar i to ime parametra se koristi u
definicijama metoda i atributa generičkog tipa na mestima gde
postoji zavisnost od vrednosti argumenta za taj parametar
• Pojavljivanja imena parametra u definiciji generičkog tipa nazivaju
se tipske promenljive.
Definisanje generičkog klasnog tipa
• Generalno je najbolje da imena parametara budu što je moguće
kraća – a idealno jednoslovna. U tekstu će na ime generičkog tipa
biti nadovezivano i <> radi lakšeg razlikovanja generičkog tipa od
obične klase ili tipa interfejsa.
• Moguće je definisati generički klasni tip, ali takođe i generički
interfejsni tip. Priličan broj generičkih interfejsnih tipova moguće je
naći u standardnim paketima. Kasnije ćemo raditi sa Iterable<> i
Comparable<> generičkim interfejsnim tipovima iz paketa java.lang
• Da bismo kreirali klasu od generičkog tipa PovezanaLista<> samo
obezbedimo odgovarajući argument za parametar između <>
zagrada. Sva pojavljivanja tipske promenljive T u definiciji biće
zamenjena datim tipom argumenta. To će rezultirati klasnim tipom
koji možemo koristiti da kreiramo objekat koji implementira
povezanu listu za smeštanje objekata zadatog tipa
• PovezanaLista <String>, PovezanaLista <Point>
Argumenti generičkog klasnog tipa
• Tako, generički tip u suštini definiše familiju tipova koja se dobija
prosleđivanjem različitih argumenata za parametre generičkog tipa.
• Kao argument za tipski parametar u generičkom tipu može se staviti
samo klasni ili interfejsni tip
• Drugim rečima, kao argument se ne može koristiti primitivni tip poput
int i double, iako se, naravno, može koristiti tip Integer ili tip Double.
• Kada kreiramo određeni tip od definicije generičkog tipa
prosleđivanjem vrednosti argumenta za T, argument će zameniti
svaku pojavu T-a u specifikaciji generičkog tipa. To se odnosi i na
atribute i na definicije metoda u generičkom tipu.
• Definicija generičkog tipa smešta se u izvorni fajl sa ekstenzijom
.java, upravo kao i obična klasa.
Implementiranje generičkog tipa

• PovezanaLista.java
• Klasa ClanListe je unutrašnja ( ugnježdena nestatička )
• Definicija generičkog tipa može sadržati obične metode koji ne
uključuju nikakve parametre u svojim definicijama kao i metode sa
parametrima
• Sada imamo generički tip PovezanaLista<T> koji možemo koristiti
da kreiramo novu klasu PovezanaLista za smeštanje objekata
proizvoljnog tipa. Pogledajmo kako da ga koristimo
Instanciranje generičkog tipa
• Da bismo definisali novi tip, koristimo ime generičkog tipa praćeno
imenom klasnog ili interfejsnog tipa unutar <> zagrada. Npr.
PovezanaLista<String> stringovi; // prom. tipa PovezanaLista<String>
• Ovo samo definiše promenljivu imena stringovi
• Tip ove promenljive je PovezanaLista<String>
• Kao rezultat ove naredbe, kompajler će koristiti tipski argument
String koji smo obezbedili da zameni svaku instancu tipske
promenljive T u definiciji generičkog tipa kako bi došao do definicije
za klasni tip PovezanaLista<String>
• Naravno, kada definišemo promenljivu, možemo definisati objekat:
PovezanaLista<String> stringovi = new PovezanaLista<String>();
• Ovim se poziva podrazumevani konstruktor klasnog tipa
PovezanaLista<String> da definiše objekat koji implementira
povezanu listu String objekata
Instanciranje generičkog tipa
• Argument koji prosleđujemo generičkom tipu takođe može biti i tipa
koji definišemo korišćenjem generičkog tipa. Npr.
PovezanaLista<PovezanaLista<String>> tekstovi =
new PovezanaLista<PovezanaLista<String>>();
• Ovde smo definisali povezanu listu povezanih lista
• Da bismo primenili novi generički tip PovezanaLista<> pišemo
PolyLine koristeći tip generisan iz generičkog tipa PovezanaLista<>
• TestGenericPovezanaLista
Komentarisanje programa
• Klasa Polyline kreira tip PovezanaLista<Point> od generičkog tipa
PovezanaLista<T> koji će implementirati povezanu listu Point
objekata:
private PovezanaLista<Point> polyline;
• Metodi u parametrizovanom tipu se koriste na isti način kao oni u
originalnoj definiciji klase PovezanaLista koja je implementirana bez
upotrebe generičkih klasnih tipova.
Upotreba Wrapper klasa primitivnih
tipova kao argumenata
• U slučaju da želimo da smeštamo vrednosti primitivnog tipa u
kolekciju poput povezane liste, koristimo generički tip sa nekom od
Wrapper klasa kao tipskim argumentom. To su klase Integer, Short,
Double itd. definisane u paketu java.lang
• Npr. za upotrebu generičkog tipa PovezanaLista<T> za smeštanje
vrednosti tipa double:
PovezanaLista<Double> temperature = new PovezanaLista<Double>();
• Ovde smo kreirali povezanu listu za smeštanje objekata tipa Double,
a autoboxing omogućuje da se objekat PovezanaLista<Double>
koristi direktno sa vrednostima tipa double. Npr. da bismo efektivno
dodali vrednost tipa double povezanoj listi koju smo upravo kreirali:
temperature.dodajClan(10.5);
• Pošto je tipski parametar za ovaj metod za objekat tipa
PovezanaLista<Double> tipa Double, kompajler će automatski
ubaciti boxing konverziju za konvertovanje double vrednosti 10.5 u
objekat tipa Double koji je enkapsulira.
Višestruki parametri tipa
• Generički tip PovezanaLista<T> ima jedan parametar tipa , T.
• U opštem slučaju može se definisati generički tip sa proizvoljnim
brojem parametara.
• Npr. generički tip koji definiše skup klasa koje enkapsuliraju par
objekata proizvoljnih tipova. To se tipično događa kada se 1 objekat
koristi kao ključ za pristup drugom u kolekciji. Npr.
public class Par<KljucTip, VrednostTip>{
// Konstruktor
public Par(KljucTip kljuc, VrednostTip vrednost){
this.kljuc=kljuc;
this.vrednost=vrednost;
}
// get*() i set*() metodi
private KljucTip kljuc;
private VrednostTip vrednost;
}
Višestruki parametri tipa
• Praktična definicija će biti komplikovanija od ove, npr. biće potrebno
sredstvo za poređenje ključeva, ali ova definicija je dovoljna za
demonstriranje korišćenja 2 parametra u definiciji generičkog tipa.
• Primer korišćenja ovog generičkog tipa:
Par<String,String> unos = new Par<String,String>(
"Petar Petrovic","212 222 3333");
• kreira se objekat tipa Par<String,String> i referenca na njega se
smešta u promenljivu unos.
Opseg tipskog parametra
• Opseg tipskog parametra je cela definicija generičke klase, ali
isključujući statičke članove i inicijalizatore u klasi.
• To povlači da se unutar definicije generičkog tipa ne može naći
statički atribut tipa tipske promenljive.
• Slično, statički metodi ne mogu imati parametre ili povratne tipove
koji su tipa tipske promenljive
• Tipske promenljive se ne mogu koristiti u telima definicija statičkih
metoda
• To ne znači da statički metodi ne mogu biti parametrizovani – ovde
je reč samo o tipskim promenljivim odgovarajućim za parametre
definicije generičkog tipa, a postoje i tzv. generički metodi koji imaju
svoju nezavisnu parametrizovanu definiciju koja uključuje sopstveni
skup parametara, i takvi parametrizovani metodi mogu biti statički ili
nestatički
Generički (parametrizovani) metodi
• Moguće je definisati metod sa sopstvenim nezavisnim skupom od 1
ili više tipskih parametara, čime se dobija parametrizovani metod ili
generički metod
• Možemo imati parametrizovane metode i u običnoj klasi
• Metodi unutar definicije generičkog tipa takođe mogu imati
nezavisne parametre. Npr.
public static <T> void listAll(PovezanaLista<T> list){
for(T obj: list)
System.out.println(obj);
}
Generički (parametrizovani) metodi
• <T> nakon ključnih reči public i static je lista tipskih parametara za
generički metod. Ovde imamo samo jedan tipski parametar, T, ali
može ih biti i više.
• Lista tipskih parametara za generički metod se uvek ograđuje <>
zagradama i treba da prati eventualne modifajere poput public i
static i treba da prethodi povratnom tipu.
• Generički konstruktori: konstruktor je specijalizovana vrsta metoda i
može se definisati i konstruktor sa sopstvenim nezavisnim
parametrima. Parametrizovani konstruktori se mogu definisati i za
generičke i za obične klasne tipove
Parametrizovani tipovi i nasleđivanje
• Klasu je moguće definisati kao potklasu klasnog tipa koji je instanca
generičkog tipa
• Metodi i atributi će biti nasleđeni od bazne klase na uobičajeni način
• Mora se voditi računa da se u izvedenoj klasi ne definišu metodi koji
imaju isti potpis kao neki nasleđeni metod.

Nizovi i parametrizovani tipovi


• Nizovi elemenata tipa dobijenog od generičkog tipa nisu dopušteni.
Sledeća naredba će rezultovati porukom kompajlera o grešci:
PovezanaLista<String>[] liste = new PovezanaLista<String>[10];

• private T[] data; // ovo je ok


• data = new T[10]; // Nije dopusteno!!!
Generički tipovi i generički interfejsi
• Generički tip može implementirati 1 ili više interfejsnih tipova,
uključujući generičke interfejsne tipove.
• Sintaksa koja se koristi za ovo je ista kao za obične klase i
interfejsne tipove, jedina razlika je što će ime svakog generičkog
tipa biti praćeno njegovom listom tipskih parametara između <>
zagrada. Npr.
public class MyClass<T> implements MyInterface<T>{
// Detalji definicije generickog tipa …
}
Kolekcije i collection-based for petlja
• Da bi objekat kontejnerskog klasnog tipa bio upotrebljiv sa
collection-based for petljom, klasa mora da ispuni jedan zahtev –
mora implementirati generički interfejs Iterable<> koji je definisan u
paketu java.lang.
• Interfejs Iterable<> je generički tip koji deklariše 1 jedini metod
iterator(), koji vraća referencu tipa Iterator<> koji je još jedan
generički interfejsni tip
Java Collections Framework
• Java Collections Framework se sastoji od generičkih tipova koji
predstavljaju skupove kolekcijskih klasa.
• Ovi generički tipovi definisani su u paketu java.util i obezbeđuju nam
mnoštvo načina za struktuiranje i rukovanje kolekcijama objekata u
našim programima.
• Posebno, tipovi kolekcija nam omogućuju da se nosimo sa
situacijama kada ne znamo unapred koliko ćemo objekata imati ili
kada nam je potrebno više fleksibilnosti u načinu na koji pristupamo
objektu kolekcije nego što imamo indeksiranjem niza
Java Collections Framework
• Kolekcijska klasa je prosto klasa koja organizuje skup objekata
datog tipa na određeni način, poput povezane liste ili steka
• Korišćenje generičkog tipa za naše kolekcije objekata znači da
imamo statičku proveru od strane kompajlera tipova koje želimo da
obrađujemo. Time se obezbeđuje sprečavanje nemarnih pokušaja
smeštanja objekata pogrešnog tipa u kolekciju ( kolekcija je type-
safe)
• Collections Framework uključuje profesionalnu implementaciju
generičkog tipa koji implementira povezanu listu koja je daleko
superiornija od povezane liste koju smo sami implementirali.
Međutim, naš trud nije bio uzaludan, pošto sada imamo dobru ideju
o tome kako funkcionišu povezane liste i kako se definišu generički
tipovi.
Java Collections Framework
• Otkrićemo da je Collections Framework važan faktor u većini naših programa. Kada
želimo niz koji se automatski proširuje kako bi se prilagodio proizvoljnom broju
objekata koji ubacujemo u njega, možemo koristiti odgovarajući generički tip
implementiran u Collections Framework-u.
• U Collections Framework-u ima toliko toga, ali potrudićemo se da pogledamo kako se
primenjuju neki reprezentativni primerci kolekcija koji će nam verovatno najčešće
trebati:
Iterator<T> interfejsni tip, deklariše metode za iteriranje kroz
elemente kolekcije, jedan po jedan
Vector <T> tip, nizolika struktura za smeštanje proizvoljnog tipa
objekata. Broj objekata koje možemo smestiti
automatski se povećava po potrebi
Stack <T> tip, podržava smeštanje proizv. tipa objekata na stek
LinkedList <T> tip, podržava smeštanje proizv. tipa objekata u
dvostruko povezanu listu
HashMap <K,V> tip, podržava smeštanje objekata tipa V u heš tabelu
koju nazivamo katalogom/mapom. Objekat se smešta
korišćenjem pridruženog ključa koji je objekat tipa K. Da
bismo pristupili objektu, moramo proslediti njegov ključ
Kolekcije objekata, opšta razmatranja
• Objekat tipa PovezanaLista <T> predstavlja primer kolekcije objekata tipa T,
gde T može biti proizvoljni klasni ili interfejsni tip
• Kolekcija je izraz koji se koristi da opiše objekat koji predstavlja skup objekata
grupisanih zajedno i organizovanih na određeni način u memoriji.
• Klasa koja definiše kolekciju objekata često se naziva kontejnerskom klasom.
• Postoje 3 osnovna tipa kolekcija za organizovanje objekata na razne načine:
skupovi (sets), nizovi (sequences) i mape/katalozi (maps)
• Kada pričamo o kolekcijama objekata, zapravo mislimo na kolekcije referenci
na objekte. U Javi, kolekcije sadrže samo reference na objekte, sami objekti su
eksterni za kolekciju
Skupovi
• najjednostavnija vrsta kolekcije
• objekti nisu uređeni ni na koji način i jednostavno se dodaju skupu bez ikakve
kontrole gde će se smestiti. To je kao kada stavljamo stvari u džep – samo ih
stavimo unutra
• možemo dodavati objekte skupu i iterirati kroz sve objekte skupa
• takođe, možemo proveriti da li je objekat element skupa ili ne
• u skupu nema ponavljanja elemenata – svaki objekat u skupu mora biti
jedinstven
• možemo i ukloniti dati element iz skupa, ali samo ako imamo referencu na taj
objekat u skupu
• postoje i varijacije skupova – npr. skup može biti uređen. Takvi skupovi
zahtevaju da objekti koji se smeštaju u skup budu tipa klase koja definiše
pogodne metode za poređenje objekata
Nizovi
• Povezana lista je primer opštijeg tipa kolekcije, niza.
• Osnovna karakteristika niza je da se objekti smeštaju linearno, ne nužno u
određenom redosledu, ali su organizovani u neki proizvoljni fiksirani redosled
gde se zna početak i kraj
• običan niz je takođe primer niza, ali sa mnogo većim ograničenjima u odnosu
na ekvivalentnu kolekciju pošto sadrži fiksiran broj elemenata
• Kolekcije generalno imaju mogućnost da se šire kako bi se prilagodile
potrebnom broju elemenata
• Pošto je niz linearan, novi objekat se može dodati samo na početak ili na kraj ili
nakon datog objekta u nizu.
• Generalno, objektu niza se može pristupiti na nekoliko načina: možemo izabrati
prvi ili poslednji, objekat na datoj poziciji – indeksiranje niza, zatim, možemo
tražiti objekat jednak datom objektu proverom svih objekata niza bilo unapred,
ili unazad
• Za uklanjanje objekta iz niza imamo iste opcije kao za pristup objektu: možemo
ukloniti prvi ili poslednji, objekat sa date pozicije ili objekat jednak datom
objektu
• Nizovi mogu sadržati nekoliko kopija istog objekta na različitim mestima u nizu
Stek
• Stek ( stack ) last-in first-out (LIFO) struktura je takođe niz
• Red (queue) first-in first-out (FIFO) struktura
• Jednostavno se vidi da se povezana lista ponaša kao stek, pošto korišćenje
metoda za dodavanje objekata na kraj liste i uklanjanje objekata sa kraja liste
čini da se lista ponaša kao stek
• Slično, samo dodavanje objekata korišćenjem metoda za dodavanje objekata
na kraj povezane liste i samo uzimanje objekata sa početka liste čini da se ona
ponaša kao FIFO red
• U Java Collections Framework tipovi koji definišu nizove podeljeni su u 2
grupe: liste i redove
• Vektori, povezane liste i stekovi su liste, a queue(FIFO) je red
Mape/Katalozi
• Mape se prilično razlikuju od kolekcija skupova i nizova, pošto svaki njihov
unos uključuje par objekata
• Mape se ponekad zbog načina na koji funkcionišu nazivaju i rečnicima
• Svaki objekat smešten u mapi ima pridružen objekat ključ i objekat i njegov
ključ se smeštaju zajedno, u paru.
• Ključ određuje gde će objekat biti smešten u mapi, i kada želimo da mu
pristupimo, moramo proslediti odgovarajući ključ ( ključ je ekvivalent reči koju
tražimo u rečniku )
• Ključ može biti proizvoljna vrsta objekta koju želimo da koristimo za referisanje
objekata iz mape
• Pošto ključ mora jedinstveno da identifikuje objekat, svi ključevi u mapi moraju
biti različiti
• Ključ je mehanizam za pristupanje objektima
HEŠIRANJE:
• Gde će par ključ/objekat biti smešten u mapi određeno je procesom koji se
zove heširanje
• Heširanjem se od ključa dobija celobrojna vrednost koja se zove heškod
(hashcode)
• Metod hashCode() definisan u klasi Object proizvodi heškod tipa int za objekat.
Iteratori
• Iterator je objekat koji možemo da koristimo jedanput za pristup svim objektima
kolekcije, jednom po jednom.
• Korišćenje iteratora je standardni mehanizam za pristup svim elementima
kolekcije
• Svaki objekat kolekcije koji predstavlja skup ili niz može kreirati objekat tipa
Iterator<> koji se ponaša kao iterator. Tipovi koji predstavljaju mape nemaju
metode za kreiranje iteratora
• Iterator<> objekat enkapsulira reference na sve objekte kolekcije u nekom
redosledu i njima se može pristupiti, jednoj po jednoj, korišćenjem metoda
Iterator<> interfejsa
Iteratori
• Interfejs Iterator<> ( paket java.util ) deklariše sledeća 3 metoda:
T next() vraća objekat tipa T, počev od prvog i postavlja
Iterator<T> objekat tako da vrati sledeći objekat
prilikom sledećeg poziva ovog metoda. Ako nema
objekta koji treba vratiti, metod izbacuje izuzetak
NoSuchElementException

boolean hasNext() vraća true ako postoji objekat kome će se pristupiti


next() metodom, false inače

void remove() uklanja poslednji objekat vraćen metodom next() iz


kolekcije. Ako next() nije pozvan ili pozovemo
remove() 2 puta nakon poziva next(), izbacuje se
izuzetak IllegalStateException
Ne podržavaju svi iteratori ovaj metod i ukoliko ga
pozovemo u tom slučaju, biće izbačen izuzetak
UnsupportedOperationException
Iteratori
• Poziv next() metoda za objekat koji implementira Iterator<> vraća uzastopne
objekte iz kolekcije, počev od prvog, pa se jednostavno može pomoću petlje
proći kroz celu kolekciju:
MyClass item;
while( iter.hasNext() ) {
item = iter.next();
// radimo nesto sa item …
}
• ovde se pretpostavlja da je iter tipa Iterator<MyClass> i čuva referencu na
objekat dobijen iz proizvoljne kolekcijske klase
• Većina objekata kolekcija ima metod iterator() koji vraća iterator za tekući
sadržaj kolekcije
• Metod next() vraća objekat originalnog tipa, pa nema potrebe za kastovanjem
• Svaki put kada iznova treba proći kroz objekte kolekcije, potreban je novi
iterator, pošto je iterator objekat za "jednokratnu upotrebu"
• Iterator je "jednosmerna ulica" – možemo proći kroz objekte kolekcije, 1 po 1,
jednom i to je to. To je uglavnom zadovoljavajuće, ali ne potpuno, pa imamo i
druge mogućnosti za pristup celokupnom sadržaju kolekcije
• Možemo pristupati objektima proizvoljne kolekcije koja implementira interfejs
Iterable<> koristeći collection-based for petlju.
• Ako to nije dovoljno, postoji fleksibilnija vrsta iteratora – list iterator.
List iteratori
• ListIterator<> interfejs, definisan u paketu java.util, deklariše metode za
kretanje kroz kolekciju unapred i unazad, pa se jednom objektu može pristupiti
i više od jedanput
• ListIterator<> interfejs nasleđuje Iterator<> interfejsni tip, pa se mogu
primenjivati i metodi superinterfejsa koje smo upravo videli.
• Metodi definisani u ListIterator<> interfejsu za kretanje kroz listu objekata su:
T next()
boolean hasNext() kao kod Iterator<>
int nextIndex() vraća indeks objekta koji će biti vraćen sledećim
pozivom next() kao tip int, ili vraća broj elemenata
u listi ako je ListIterator<> objekat na kraju liste
T previous() vraća prethodni objekat, koristi se za kretanje
unazad kroz listu
boolean hasPrevious() vraća true ako će sledeći poziv previous() vratiti
objekat
int previousIndex() vraća indeks objekta koji će biti vraćen sledećim
pozivom previous() ili -1 ako je ListIterator<>
objekat na početku liste
pozivi next() i previous() mogu se preplitati. Poziv previous() neposredno
nakon poziva next() i obrnuto vratiće isti element.
List iteratori
• ListIterator<> metodi za dodavanje, uklanjanje i zamenu objekata kolekcije su:
void remove() uklanja poslednji objekat kome je pristupljeno
pomoću next() ili previous()
(UnsupportedOperationException, IllegalStateException)
void add(T obj) dodaje argument neposredno ispred objekta koji bi bio
vraćen narednim pozivom next() ili iza objekta koji bi bio
vraćen narednim pozivom previous(). Poziv next() nakon
add() operacije vratiće dodati objekat. Naredni poziv
previous() nije izmenjen ovom operacijom
(UnsupportedOperationException,ClassCastException,
IllegalOperationException)
void set(T obj) menja poslednji objekat kome je pristupljeno pozivom
next() ili previous()
(IllegalStateException, UnsupportedOperationException,
ClassCastException, IllegalArgumentException)
Korišćenje vektora
• Generički tip Vector<T> funkcioniše kao niz, ali uz mogućnost da automatski
raste kada nam je potreban dodatni kapacitet
Kreiranje Vektora
Postoje 4 konstruktora:
Vector<String> a = new Vector<String>();
podrazumevani, prazan vektor sa kapacitetom za 10
objekata, kapacitet se duplira kada dodamo objekat, a
vektor je pun
Vector<String> a = new Vector<String>(100);
eksplicitno zadajemo kapacitet, opet se kapacitet
duplira, nekad neefikasno
Vector<String> a = new Vector<String>(100,10);
kapacitet će se povećavati za po 10 elemenata
poslednji konstruktor kreira objekat koji sadrži reference na objekte iz
druge kolekcije ( prima argument tipa Collection<>)
Vektori, primer
• Primer: vrlo jednostavan, smeštanje nekoliko stringova u vektor
• import java.util.Vector;
public class TestJednostavanVektor{
public static void main(String[] args){
Vector<String> imena = new Vector<String>();
String[] imena1 = {"Pera", "Mika", "Laza", "Steva"};
// Dodavanje imena u vektor
for(String ime: imena1)
imena.add(ime);
// Prikaz sadrzaja vektora
for(String ime: imena)
System.out.println(ime);
}
}
Vektori, primer
• Sve kolekcijske klase koje su nizovi implementiraju Iterable<> interfejs, pa uvek
možemo koristiti collection-based for petlju za pristup sadržaju kolekcije.
• Takođe, može se koristiti i iterator
• Sledeći kod proizvešće isti rezultat kao poslednja for petlja:
java.util.Iterator<String> iter = imena.iterator();
while( iter.hasNext() )
System.out.println( iter.next() );
• Za pristup elementima vektora postoji i treći mehanizam. Metod get() Vector<>
objekta vraća referencu na objekat čiji je indeks dat kao argument metoda.
Argument get() metoda je indeks ( koji kreće od nule ). Da bismo iterirali kroz
sve elemente vektora moramo znati koliko elemenata imamo u vektoru, a to
možemo dobiti metodom size().
for(int i=0; i<imena.size(); i++)
System.out.println(imena.get(i));
• Collection-based for petlja je najjednostavniji i najčistiji mehanizam za
iteriranje kroz sadržaj vektora.
• get() metod je koristan za pristup elementu na određenoj poziciji
Kapacitet i veličina vektora
• Kapacitet vektora je maksimalan broj objekata koje on može sadržati u datom
trenutku
• int imenaMax = imena.capacity(); // vraca trenutni kapacitet
• imena.ensureCapacity(150); // postavlja min kapacitet na 150
ako je tekući kapacitet manji od 150, porašće na 150, a ako je 150 ili veći,
ova naredba ga neće promeniti
Argument je tipa int, ne postoji povratna vrednost
• Veličina vektora je broj elemenata smeštenih u taj vektor
Jasno, veličina vektora ne može biti veća od njegovog kapaciteta
• imena.setSize(50);
metodom setSize() možemo povećati i smanjiti veličinu
Veličina se postavlja na argument. Ako je veličina bila manja od 50, ostatak se
puni null referencama, a ako je bila veća od 50, ostatak se odbacuje.
Sami objekti još uvek mogu biti dostupni ako postoje druge reference na njih.
• imena.trimToSize(); // kapacitet se postavlja na velicinu
Smeštanje objekata u vektor
• imena.add(ime); novi objekat se smešta u vektor i to na kraj svih objekata
koji su već u vektoru, veličina vektora se uvećava za 1.
Svi "stari" objekti ostaju na istim pozicijama gde su bili i
pre ove operacije
• imena.add(2,ime); smeštanje objekta u vektor na poziciju zadatu prvim
argumentom. Ta pozicija mora biti manja ili jednaka
veličini vektora ( to znači da se na tom mestu već nalazi
neka referenca ili je to pozicija na kraju vektora).
Indeksiranje kreće od nule.
Novi objekat ime u ovom konkretnom slučaju se smešta
ispred objekta koji je ranije imao indeks 2, pa se taj i svi
ostali objekti sa većim indeksima pomeraju za 1 udesno
i odgovaraće im indeksi za po 1 veći nego pre ove
operacije ( ArrayIndexOutOfBoundsException )
• imena.set(2,novoIme); promena elementa u vektoru, objekat zadat drugim
argumentom smešta se na poziciju u vektoru zadatu
prvim argumentom. Metod vraća referencu na objekat
prethodno smešten na toj poziciji ( -||- izuzetak )
Smeštanje objekata u vektor
• imena.addAll(imenaLista); dodavanje svih objekata druge kolekcije u vektor
i to na njegov kraj (argument je tipa Collection<>)
• imena.addAll(i,imenaLista); -||-, ali ne na kraj, nego počev od pozicije i
(ArrayIndexOutOfBoundsException)

Pristup objektima vektora


• String ime = imena.get(4); // vraca element na zadatoj poziciji
povratni tip je određen tipskim argumentom korišćenim za kreiranje Vector<>
objekta. Indeks mora biti nenegativan i manji od veličine vektora, inače
ArrayIndexOutOfBoundsException
• String ime = imena.firstElement(); // vraca prvi element
• postoji i metod lastElement() koji vraća poslednji element
Pristup elementima vektora preko List
iteratora
• Možemo dobiti ListIterator referencu iz vektora pozivom metoda listIterator() :
• ListIterator<String> listIter = imena.listIterator();
• nakon ovoga možemo se kretati unapred i unazad koristeći ListIterator metode
koje smo ranije videli
• Moguće je dobiti i ListIterator<> objekat samo za deo vektora koristeći verziju
listIterator() metoda sa argumentom koji predstavlja indeks prvog elementa
vektora u iteratoru:
• ListIterator<String> listIter = imena.listIterator(2); (IndexOutOfBoundsException)
• List<String> lista = imena.subList(2,5); // ekstrahuje elemente 2 do 4 kao podlistu!
element sa indeksom određenim drugim argumentom se ne uključuje u listu!
(IndexOutOfBoundsException)
KOMBINACIJE:
ListIterator<String> listIter = imena.subList(5,15).listIterator(2);
prvo se pomoću subList() dobije List<String> objekat, pa on vrati list iterator tipa
ListIterator<String> metodom listIterator()
Konverzija vektora u niz
• metod toArray()
• String[] podaci = imena.toArray(new String[imena.size()]);
argument metoda mora biti niz istog tipa ili supertipa za tip elemenata vektora
( ArrayStoreException, ako je argument null – NullPointerException )

Konverzija niza u vektor


• klasa java.util.Arrays definiše statički generički metod asList() koji konvertuje
niz datog tipa T u kolekciju List<T>. Npr.
• String[] ljudi = { "Pera", "Mika", "Laza", "Steva" };
List<String> imenaLista = java.util.Arrays.asList(ljudi);
Vector<String> imena = new Vector<String>(java.util.Arrays.asList(ljudi));
Uklanjanje objekata iz vektora
• remove(3); uklanja element sa zadate pozicije, elementi iza se pomeraju za po
1 mesto ulevo ( IndexOutOfBoundsException)
Vraća se referenca na uklonjeni objekat.
String ime = imena.remove(3);

• boolean izbrisan = imena.remove(ime);


pretražuje vektor imena od početka i traži prvu referencu na objekat ime i
uklanja je. Ako je objekat pronađen i uklonjen iz vektora, vraća true, a
inače false
• removeAll() prihvata argument tipa Collection<> i uklanja elemente te kolekcije
ako su prisutni u vektoru. Metod vraća true ako je Vector objekat promenjen
ovom operacijom ( bar 1 el. uklonjen ). Može se koristiti u kombinaciji sa
metodom subList() da ukloni određeni skup elemenata:
imena.removeAll(imena.subList(5,15));
ukloniće elemente 5 do 14, uključujući, iz Vector<String> objekta imena,
plus eventualne duplikate tih elemenata ako postoje u vektoru
Uklanjanje objekata iz vektora
• retainAll() metod očekuje kao argument referencu na tip Collection<> koja
sadrži objekte koje treba zadržati. Svi elementi koji nisu u toj kolekciji biće
obrisani
imena.retainAll(imena.subList(5,15));
vraća true ako je vektor promenjen
• imena.removeAllElements(); uklanja sve elemente vektora, veličina se
postavlja na 0
• može i imena.clear();
• boolean prazan = imena.isEmpty(); vraća true ako je veličina 0, false inače
• Vector<> objekat može sadržati null reference, ali to ne znači da će size() biti 0
ili metod isEmpty() vratiti true. Da bismo ispraznili Vector<> objekat, moramo
zaista ukloniti sve elemente, a ne samo postaviti ih na null.
Pretraživanje vektora
• Možemo dobiti poziciju objekta smeštenog u vektoru prosleđujući ga kao
argument metodu indexOf():
int pozicija = imena.indexOf(ime);
će pretraživati vektor imena od početka tražeći objekat ime, koristeći metod
equals za argument, pa klasa objekta ime mora imati odgovarajuću
implementaciju metoda equals() da bi ovo radilo. Promenljiva pozicija će
dobiti ili indeks prve reference ili -1 ako objekat nije pronađen
• indexOf(ime,5) - slično, ali traži počev od indeksa 5
• npr. ( računa se broj pojavljivanja datog imena u datom vektoru )
String ime = "Pera"; // ime koje se trazi
int count = 0; // broj pojavljivanja
int position = -1; // pocetni index
while ( ++position < imena.size() ){
if((position=imena.indexOf(ime,position))<0)
break;
++count;
}
Primena vektora
• Guzva
• Program za modeliranje kolekcije ljudi, imena osoba će se unositi sa tastature
• Alternative za ispis:
for(int i=0; i<filmCast.size(); i++)
System.out.println(filmCast.get(i));

for(Osoba osoba: filmCast)


System.out.println(osoba);
• Statički metod ucitajOsobu() je pogodan način rukovanja ulazom
Sortiranje kolekcije
• paket java.util
• klasa Collections ( ne interfejs Colleciton<> ! )
• ima mnoštvo statičkih metoda koji se mogu primeniti na kolekcije
• 1 od njih je i metod sort()
• metod sort() sortira isključivo liste, tj. kolekcije koje implementiraju interfejs
List<> ( a to su Vector<>, Stack<> i LinkedList<> )
• Očigledno, mora da postoji način na koji će sort() metod utvrditi redosled
objekata liste koju sortira, u našem slučaju Osoba objekata
• Najpogodniji način je da klasa Osoba implementira interfejs Comparable<>
• Interfejs Comparable<> deklariše samo 1 metod, compareTo(). On vraća -1, 0 ili
+1 u zavisnosti od toga da li je tekući objekat manji, jednak ili veći od
argumenta prosleđenog metodu.
• Ako je Comparable<> interfejs implementiran za tip objekata smeštenih u
kolekciji, možemo samo proslediti objekat kolekciju kao argument sort()
metodu. Kolekcija se sortira "u mestu", pa ovaj metod nema povratnu
vrednost.
Sortiranje kolekcije
• Comparable<> interfejs se može jednostavno implementirati u klasi Osoba:
• public class Osoba implements Comparable<Osoba>{
// ….
// poredjenje Osoba objekata
public int compareTo(Osoba osoba){
int rezultat = prezime.compareTo(osoba.prezime);
return rezultat==0 ? ime.compareTo(((Osoba)osoba).ime):rezultat;
}
// …
}
• koristimo compareTo() metod za String objekte da uporedimo prezimena, pa ako
su ona jednaka, rezultat određujemo na osnovu imena
Sortiranje kolekcija
• Možemo samo proslediti Vector<Osoba> objekat metodu sort() i koristiće se
metod compareTo() klase Osoba za poređenje članova liste:
• import java.util.Collections;
... main...{
// sve kao i ranije …
Collections.sort(filmCast);
System.out.println("\nU abecednom poretku:\n");
for(Osoba osoba: filmCast)
System.out.println(osoba);
• sort() metod je u stvari generički metod i on radi sa proizvoljnim tipom koji
implementira Comparable<> interfejs
Stek
• LIFO ( last-in first-out)
• generički tip Stack<> je izveden iz Vector<>
• Klasa Stack<> dodaje 5 metoda onima nasleđenim iz Vector<>
T push(T obj) stavlja objekat obj tipa T na vrh steka, vraća referencu
koju smo prosledili kao argument
T pop() skida objekat sa vrha steka i vraća ga. Referenca se
uklanja sa steka. Ako je stek prazan – EmptyStackException
T peek() vraća element sa vrha steka, ali ga ne skida sa steka
(EmptyStackException)
int search(Object obj) vraća poziciju na steku reference objekta obj.
referenca na vrhu steka je na poziciji 1, sledeća na
pozicji 2, itd. Ako objekat nije pronađen na steku,
vraća se -1.
boolean empty() vraća true ako je stek prazan, false inače
Stek
• Jedini konstruktor je konstruktor bez argumenata
• on poziva podrazumevani konstruktor bazne klase Vector<>, pa je inicijalni
kapacitet 10 objekata, ali će automatski rasti na isti način kao za vektor
• push() metod za Stack<> objekat analogan je add() za Vector<>, što dodaje
objekat na kraj vektora. Tako, vrh steka odgovara kraju vektora
Stek, primer
• DeljenjeKarata
• Možemo koristiti Stack<> objekat, zajedno sa još jednim korisnim metodom
klase Collections kako bismo simulirali deljenje karata iz špila.
• Potreban je i način predstavljanja boja karata i njihovih vrednosti. Tip enum je
zadovoljavajući za oba jer ima fiksiran skup konstantnih vrednosti
• Karta.java
• Klasa Karta ima 2 atributa, i oba su tipa enumeracije
• String reprezentacija konstante enumeracije je ime koje smo pridružili toj
konstanti!
• U opštem slučaju, verovatno će nam trebati da poredimo karte, pa klasa Karta
može da implementira interfejs Comparable<>
• compareTo() metod u klasi Karta: ako su dve karte iste boje, porede se
vrednosti. Za poređenje enum vrednosti na jednakost koristimo metod equals()
• Klasa Enum<> koja je bazna za sve enum tipove implementira Comparable<>
interfejs, pa koristimo compareTo() metod za utvrđivanje redosleda enum
vrednosti
Stek, primer
• Ruka.java
• Ruku karata podeljenu iz špila možemo predstaviti objektom tipa Ruka. Taj
objekat mora da može da se prilagodi proizvoljnom broju karata, pošto to zavisi
od igre za koju je ruka namenjena.
• Možemo definisati klasu Ruka koristeći Vector<Karta> objekat za smeštanje
karata.
• Podrazumevani konstruktor generisan kompajlerom kreiraće Ruka objekat koji
sadrzi Vector<Karta> član, ruka
• dodajemo metod sort() klasi Ruka koji sortira karte u ruci
• Klasa Karta implementira interfejs Comparable<> pa možemo koristiti statički
metod sort() klase Collections za sortiranje karata u ruci
• return this; vraća tekući Ruka objekat nakon što je sortiran
• U opštem slučaju možda želimo da poredimo ruke, ali to potpuno zavisi od
konteksta. Najbolji pristup za ovo bio bi da se iz Ruka izvede ruka specifična za
datu igru, npr. PokerRuka i implementira metod compareTo() interfejsa
Comparable<> u toj klasi na način koji odgovara datoj igri.
Stek, primer
• Spil.java
• Špil je Stack<Karta> objekat spil
• metod values() za tip enum vraća kolekciju koja sadrži sve konstante
enumeracije
• podeliRuku() metod kreira Ruka objekat i zatim skida brojKarata karata sa steka
spil i dodaje svaku od njih ruci. Zatim se vraća Ruka objekat
• Potreban nam je metod za mešanje karata pre deljenja
• import java.util.Collections;
...
Collections.shuffle(spil);
...
• metod shuffle() klase Collections meša sadržaj proizvoljne kolekcije koja
implementira List<> interfejs.
• Klasa Stack<> implementria interfejs List<>, pa možemo koristiti metod
shuffle() da dobijemo promešani špil Karta objekata.
• ako koga zanima, vreme potrebno za izvršavanje metoda shuffle() je
proporcionalno broju elemenata – ide se unazad kroz listu i swap-uje tekući
element sa slučajno izabranim elementom između prvog i tekućeg
Stek, primer
• TestDeljenje.java
• Ruka mojaRuka = spil.podeliRuku(5).sort();
• podeliRuku() metod vraća Ruka objekat pomoću koga zovemo njegov metod
sort()
• Pošto metod sort() vraća referencu na Ruka objekat nakon sortiranja, moguće
je u 1 naredbi izvršiti deljenje ruke, sortiranje i naredbu dodele
• Stek objekat je posebno podesan za deljenje karata, pošto želimo da uklonimo
svaku kartu sa špila pošto je podelimo, a to se automatski radi pop() metodom
• Kada treba da prođemo kroz sve objekte steka, bez njihovog uklanjanja,
koristimo collection-based for petlju, kao sa Vector<Karta> objektom u
toString() metodu klase Ruka. Pošto je klasa Stack<> izvedena iz Vector<>, svi
metodi klase Vector<> dostupni su i za stek.
• Korišćenje steka je veoma jednostavno, a stek je moćna alatka u mnogim
različitim kontekstima. Stek je često primenjen u aplikacijama koje uključuju
sintaksnu analizu, poput kompajlera i interpretera – uključujući i one za Javu
Povezane liste
• LinkedList<> generički tip
• implementira uopštenu povezanu listu
• 2 konstruktora:
– podrazumevani – kreira praznu listu
– sa argumentom tipa Collection<> koji kreira LinkedList<> objekat koji će
sadržati objekte kolekcije prosleđene kao argument
• add(), addAll()
• addFirst(), addLast()
• get(), getFirst(), getLast()
• remove(), removeFirst(), removeLast()
• set()
• size()
• Kao i za Vector<> objekat, možemo dobiti Iterator<> pozivom iterator() metoda,
a ListIterator<> objekat pozivom listIterator()
• Iterator<> objekat dopušta samo kretanje unapred kroz elemente, dok
ListIerator<> objekat omogućuje kretanje u oba smera
Povezane liste, primer
• PRIMER: Možemo izmeniti primer TestPolyline da koristi LinkedList<> objekat,
a ne našu "uradi-sam" verziju.
• TestPolyLine i Point klase ostaju iste kao i pre, potrebno je samo promeniti
definiciju klase Polyline
• Klasa je sada dosta jednostavnija pošto klasa LinkedList<> obezbeđuje svu
mehaniku operisanja povezanom listom
• Glavna izmena se tiče klase Polyline i Point objekti su sada smešteni u
povezanoj listi implementiranoj LinkedList<> objektom, polyline.
• Korišćenje kolekcijske klase čini klasu Polyline vrlo pravolinijskom
• Primer korišćenja collection-based for petlje sa dvodimenzionim nizom:
• public Polyline(double[][] coords){
for(double[] xy: coords)
addPoint(xy[0],xy[1]);
}
• Dvodimenzioni niz je efektivno jednodimenzioni niz referenci na
jednodimenzione nizove od kojih svaki ima po 2 elementa – x i y koordinatu
tačke.
• loop-promenljiva xy je tipa double[] i ima 2 elementa
Korišćenje mapa
• Mapa je način smeštanja podataka koji minimizuje potrebu za
pretraživanjem kada želimo da pristupimo objektu
• Svaki objekat ima pridružen ključ koji se koristi za određivanje gde
smestiti referencu na objekat, i objekat i ključ se smeštaju zajedno u
mapu
• Sa datom vrednošću za ključ, uvek se manje-više direktno dolazi do
objekta odgovarajućeg za taj ključ
Proces heširanja
• Implementacija mape u Java collections framework obezbeđena
klasom HashMap<> odvojena je od niza u kome se smeštaju
ključ/objekat parovi
• Indeks za ovaj niz dobija se od ključa korišćenjem heškoda objekta
za računanje offset-a u nizu za smeštanje parova ključ/objekat
• Po default-u, tu se koristi hashCode() metod za objekat ključ. Ovaj
metod je nasleđen u svim klasama od Object, pa je to metod koji
generiše osnovni heškod osim ako hashCode() metod nije
redefinisan u klasi za ključ. HashMap<> klasa ne pretpostavlja da je
osnovni heškod adekvatan, pa se on dalje transformiše unutar
HashMap<> objekta.
• Unos u tabeli koja se koristi za smeštanje parova ključ/vrednost
zove se bucket. Heškod dobijen od ključa određuje bucket u koji će
biti smešten par ključ/vrednost.
Proces heširanja
• Iako svaki ključ mora biti jedinstven, ne mora se od svakog ključa
dobiti jedinstven heškod. Kada dva ili više različitih ključeva daju istu
heš-vrednost, to se zove kolizija. HashMap<> objekat radi sa
kolizijama tako što smešta sve ključ/vrednost parove sa istom heš-
vrednošću u povezanu listu. Ako se to dešava prečesto, očigledno
dolazi do usporavanja procesa smeštanja podataka i pristupanja
podacima.
• Pristup objektu kod koga je prilikom smeštanja u mapu došlo do
kolizije sastoji se iz 2 faze: ključ će biti heširan da se pronađe
lokacija na kojoj bi par ključ/vrednost trebalo da bude. Zatim treba
pretražiti povezanu listu, da bi se došlo do ključa koji tražimo ( u listi
su svi oni ključevi koji daju istu heš-vrednost kao i traženi ključ )
• Zato postoji jak podstrek da se minimizuju kolizije, a cena smanjenja
mogućnosti kolizija u heš-tabeli je mnogo praznog prostora u tabeli.
hashCode() metod
• Klasa Object definiše metod hashCode(), pa se svaki objekat može
koristiti kao ključ.
• Metod nije baš univerzalno primenljiv. Pošto obično koristi
memorijsku adresu na kojoj je objekat smešten za dobijanje heš-
vrednosti, različiti objekti uvek proizvode različite heš-vrednosti.
• To je povoljno jer će operacije nad heš-mapom biti efikasnije,
međutim ono što nije dobro je to da različite instance objekta koje
sadrže identične podatke proizvode različite heš-vrednosti, pa ih ne
možemo porediti.
• To postaje smetnja ako koristimo default hashCode() metod u
objektima koje koristimo kao ključeve. U tom slučaju, objektu
smeštenom u heš-mapi nikada se ne može pristupiti korišćenjem
druge instance objekta ključa, čak i ako je taj objekat ključ identičan
u svim ostalim aspektima. A upravo je to slučaj u većini situacija
hashCode() metod
• Npr. razmotrimo aplikaciju poput jednostavnog adresara.
• Želimo da smeštamo unose u mapu bazirano na imenima ljudi na
koje se unosi odnose, a želimo da pretražujemo mapu na osnovu
imena koja se unose sa tastature.
• Međutim, objekat koji predstavlja novouneseno ime biće neizbežno
različit od onoga koji je korišćen kao ključ za unos i njegovim
korišćenjem nećemo moći da nađemo odgovarajući unos za to ime
• Rešenje ovog problema bilo bi da se nekako napravi heširanje
atributa objekata. Tada, poređenjem vrednosti atributa novog
objekta ime sa onima iz objekata imena korišćenih kao ključeva u
heš-mapi moći ćemo da pronađemo odgovarajući unos.
Korišćenje objekata naših klasa kao
ključeva
• Da bi objekti naših klasa mogli da se koriste kao ključevi u heš-
tabeli, mora se predefinisati (override) metod equals() klase Object.
• Metod equals() prima kao argument objekat iste klase i vraća
boolean vrednost
• Ovaj metod se koristi u metodima klase HashMap<> za utvrđivanje
da li su 2 ključa jednaka, pa naša verzija ovog metoda treba da vrati
true kada dva različita objekta sadrže identične vrednosti atributa
• Takođe, možemo predefinisati metod hashCode(), koji vraća heš-
vrednost za objekat kao tip int.
Generisanje heškodova
• To je ogromna tema. Kako ćemo pisati hashCode() metod u svojoj
klasi, zavisi od nas, ali taj metod mora ispunjavati neke zahteve da
bi bio od koristi.
• heškod koji vraća metod hashCode() je vrednost tipa int.
• Treba da se trudimo da vratimo heškod koji ima veliku verovatnoću
da bude jedinstven za objekat i heškodovi koje generišemo za
opseg različitih objekata sa kojima ćemo raditi treba da bude što je
moguće šire raspodeljen po opsegu int vrednosti.
• Da bismo postigli jedinstvenost, tipično ćemo želeti da kombinujemo
vrednosti svih atributa u objektu kako bismo dobili heškod. Prvi
korak je dobiti integer za svaki atribut. A zatim treba ukombinovati te
integer-e za dobijanje vrednosti koja će biti heškod za objekat
• Jedan način da se ovo uradi jeste da se svaki integer koji odgovara
nekom atributu pomnoži različitim prostim brojem i saberu tako
dobijeni rezultati
Generisanje heškodova
• To bi trebalo da da razumljivu distribuciju vrednosti sa dobrom
verovatnoćom da budu različite za različite objekte
• Nije bitno koje proste brojeve ćemo koristiti sve dok:
– nisu tako veliki da rezultat izađe iz opsega tipa int
– koristimo različit prost broj za svaki atribut
• Kako od atributa dobiti integer ?
Generisanje integer-a za atribute tipa String je prosto: samo
pozovemo hashCode() metod za atribut, koji je u klasi String
implementiran tako da proizvede dobre heškod vrednosti koje će biti
jednake za identične stringove.
int atribute možemo koristiti takve kakvi su, dok atributi realnih
tipova zahtevaju nešto obrade.
Generisanje heškodova - primer
• Npr. želimo da koristimo objekte klase Osoba kao ključeve u heš
tabeli, a atributi su ime i prezime tipa String i godine tipa int
• Mogli bismo hashCode() metod te klase da implementiramo sa:
public int hashCode() {
return 13*ime.hashCode() + 17*prezime.hashCode() + 19*godine;
}
• Ako je atribut objekat tipa neke klase, a ne promenljiva primitivnog
tipa, treba implementirati hashCode() metod za tu klasu i koristiti ga
u računanju heškoda za klasu ključa.
Kreiranje hash-map kontejnera
• sve map klase implementiraju Map<> interfejs, pa se objekat
proizvoljne map klase može referisati promenljivom tipa Map<>
• klasa HashMap<> je dobra za većinu situacija
• konstruktori: za kreiranje HashMap<K,V> objekta
HashMap() kreira mapu kapaciteta dovoljnog za
podrazumevani broj objekata (16 , a
podrazumevani load faktor je 0.75)
HashMap(int capacity) kreira mapu zadatog kapaciteta, load
faktor je 0.75
HashMap(int capacity, float loadFactor) kreira mapu zadatog
kapaciteta i load faktora
kreira mapu sa istim kapacitetom i load faktorom kao
što ima mapa prosleđena kao argument
Kreiranje hash-map kontejnera
• Primer: kreiranje mape podrazumevanim konstruktorom:
HashMap<String, Osoba> mapa = new HashMap<String, Osoba>() ;
ovom naredbom se kreira HashMap<> objekat za smeštanje Osoba
objekata zajedno sa pridruženim ključevima tipa String
• capacity za mapy je broj ključ/objekat parova koje ona može da
smesti. Kapacitet se automatski povećava po potrebi, ali to je
vremenski zahtevna operacija. capacity vrednost za mapu kombinuje
se sa heškodom ključa koji zadamo da bi se izračunao indeks koji
određuje gde će objekat i njegov ključ biti smešteni. Da bi ovo
izračunavanje dalo dobru distribuciju vrednosti indeksa, kada sami
zadajemo kapacitet heš-tabele, trebalo bi da koristimo proste brojeve,
npr.
HashMap<String, Osoba> mapa = new HashMap<String, Osoba>(151);
Kreiranje hash-map kontejnera
• Broj smeštenih objekata nikada ne može dostići kapacitet. Uvek je
potrebno odvojiti kapacitet za efikasnost operacija. Sa nedovoljno
tog dodatog kapaciteta, kolizije postaju verovatnije
• load-faktor se koristi za odlučivanje kada povećati veličinu heš-
tabele. Kada veličina tabele dostigne vrednost koja je jednaka
proizvodu load-faktora i kapaciteta, kapacitet će automatski biti
uvećan na dvostruki stari kapacitet +1 ( +1 obezbeđuje da je novi
kapacitet bar neparan, ako ne i prost).
• 0.75 je dobar kompromis, a ako hoćemo da ga smanjimo, možemo
koristiti 3. konstruktor:
HashMap<String, Osoba> mapa =
new HashMap<String, Osoba> ( 151, 0.6f ) ;
Smeštanje, pristup i uklanjanje
objekata
• sve ove operacije sa heš-mapom su jednostavne.
• odgovarajući metodi su:
V put(K key, V value) smešta objekat value u mapu
korišćenjem ključa zadatog kao prvi
argument, value će ukloniti eventualni
postojeći objekat i biće vraćena
referenca na taj stari objekat. Ako
prethodno nije smešten objekat ili je
smešten null, vratiće se null.
void putAll( ___ map) prenosi sve parove ključ/objekat iz
map u tekuću mapu, zamenjujući sve
postojeće objekte sa istim ključevima
V remove(Object key) uklanja unos pridružen key ako postoji
i vraća referencu na objekat. Vraća se
null ako ne postoji takav unos ili je null
smešten koristeći key.
V get(Object key) vraća objekat smešten pomoću key. Ako pomoću
key nisu smeštani objekti u mapu ili je smešten null
objekat, vraća null. Objekat ostaje u tabeli
Smeštanje, pristup i uklanjanje
objekata
• metod containsKey() prima key objekat kao argument i vraća true
ako je taj ključ smešten u mapu. Pomoću ovog metoda može se
utvrditi da li je null smešten u mapu pomoću datog ključa ili taj ključ
uopšte nije korišćen za smeštanje objekata u mapu
• Neophodno je osigurati da je vrednost koju vrati put() jednaka null.
Inače, možemo nesvesno ukloniti objekat koji je smešten u tabelu
korišćenjem istog ključa.
• Sledeći fragment koda ilustruje kako bi se to moglo uraditi:
HashMap<String, Integer> mapa = new HashMap<String,Integer>();
String kljuc = "Perica";
int vrednost = 12345;
Integer staraVrednost = null;
for(int i=0; i<4; i++)
if( (staraVrednost = mapa.put(kljuc, vrednost++)) != null )
System.out.println("odbacili smo vrednost " + staraVrednost);
Smeštanje, pristup i uklanjanje
objekata
• Ovde smo mogli izbaciti svoj izuzetak umesto ispisa poruke
• Drugi parametar put() metoda za objekat mapa biće tipa Integer pa
kompajler obezbeđuje autoboxing konverziju int vrednosti
prosleđene kao argumenta
• 12345
12346
12347
• Kada se smešta prva vrednost, ništa nije smešteno od ranije u mapi
za taj ključ, pa nema poruke. Za sve uzastopne pokušaje smeštanja
objekata koji slede, prethodni objekat se uklanja i vraća se referenca
na njega
• get() operacija vraća referencu na objekat pridružen ključu, ali ga ne
uklanja iz tabele
Smeštanje, pristup i uklanjanje
objekata
• Da bismo pristupili objektu i obrisali unos koji ga sadrži iz tabele,
moramo koristiti metod remove().
int vrednost = mapa.remove(kljuc);
ovo uklanja objekat koji odgovara kljuc-u i vraća referencu na njega
Ako se ova naredba nadoveže na prethodni fragment koda, biće
vraćena referenca na Integer objekat koji enkapsulira vrednost
12348. Pošto to smeštamo u promenljivu tipa int, kompajler će
ubaciti unboxing konverziju.
Procesiranje svih elemenata mape
• Interfejs Map<> obezbeđuje 3 načina za dobijanje kolekcije sadržaja
mape.
• Moguće je dobiti sve ključeve Map<K,V> objekta kao objekat tipa
Set<K>.
• Takođe, moguće je dobiti Collection<V> objekat referenci na sve
objekte mape
• ključ/objekat parovi smešteni su u mapi kao objekti tipa koji
implementira Map.Entry<K,V> interfejs. To je generički interfejsni tip
definisan unutar Map<K,V> interfejsa. Možemo dobiti sve parove
ključ/objekat iz mape kao objekat tipa Set<Map.Entry<K,V>>
• Set<> ili Collection<> objekat koji dobijemo je u suštini pogled na
sadržaj mape, tako da se promene HashMap<> objekta odražavaju
na pridruženi Set<> ili Collection<> , i obrnuto.
Procesiranje svih elemenata mape
• Metodi:
keySet() vraća Set<K> objekat referenci na ključeve mape
entrySet() vraća Set< Map.Entry<K,V> > objekat referenci na
ključ/objekat parove; svaki par je objekat tipa
Map.Entry<K,V>
values() vraća Collection<V> objekat referenci na objekte
smeštene u mapi
• Set<K> objekat koji dobijemo keySet() metodom možemo koristiti ili
direktno za pristup ključevima ili indirektno za dobijanje objekata
smeštenih u mapi
Procesiranje svih elemenata mape
• Za HashMap<String, Integer> objekat mapa, možemo dobiti skup
svih ključeva mape sa:
Set<String> kljucevi = mapa.keySet();
• Onda možemo dobiti iterator za ovaj skup ključeva sa:
Iterator<String> kljucIter = kljucevi.iterator();
• možemo koristiti metod iterator() za objekat kljucevi za iteriranje
preko svih ključeva mape
• Moguće je i ukombinovati ove 2 operacije za direktno dobijanje
iteratora. Npr.
Iterator<String> kljucIter = mapa.keySet().iterator();
while(kljucIter.hasNext())
System.out.println(kljucIter.next());
Procesiranje svih elemenata mape
• Možemo koristiti ključeve za izdvajanje objekata, ali Collection<>
objekat koji vrati metod values() obezbeđuje direktniji način za to.
• Primer listanja objekata smeštenih u mapa, pod pretpostavkom da je
ona tipa HashMap<String, Integer> :
Collection<Integer> kolekcija = mapa.values();
for(Integer i: kolekcija)
System.out.println(i);
• Interfejs Set<> ima Iterable<> za superinterfejs, pa možemo koristiti
collection-based for petlju direktno sa objektom koji vrati metod
keySet():
Set<String> kljucevi = mapa.keySet();
for(String kljuc: kljucevi)
System.out.println(kljuc);
• mnogo zgodnije i čitljivije nego sa iteratorom, zar ne ?
uopšte, collection-based for petlja daje lakše razumljiv kôd nego
iterator
Procesiranje svih elemenata mape
• Na sličan način kao za skup ključeva, možemo koristiti for petlju za
pristup Map.Entry<> objektima Set<Map.Entry<K,V>> objekta koji
vrati metod entrySet().
• Za operisanje Map.Entry<K,V> objektima na raspolaganju su nam
sledeći metodi:
K getKey() vraća ključ za Map.Entry<K,V> objekat
V getValue() vraća objekat za Map.Entry<K,V> objekat
V setValue( V new ) postavlja objekat tekućeg Map.Entry<K,V>
objekta na argument i vraća originalni objekat.
Ovo menja originalnu mapu.
(UnsupportedOperationException – ako mapa
ne podržava put(), ClassCastException,
IllegalArgumentException)
Procesiranje svih elemenata mape
• Objektu Map.Entry<> takođe je potreban equals() metod za
poređenja sa drugim Map.Entry<> objektom prosleđenim kao
argumentom, i hashCode() metod za računanje heškoda za
Map.Entry<> objekat.
• Sa skupom Map.Entry<> objekata možemo pristupati ključevima i
odgovarajućim objektima i menjati objekat-deo svakog ključ/objekat
para, ako je to potrebno
Primer, heš-mape, Imenik
• Primer: vrlo jednostavan imenik koji koristi mapu. Nećemo previše
brinuti o oporavku od greške da ne bismo opterećivali kod
• Imenik
• klase: Osoba, BrojTelefona, Unos – klasa koja predstavlja unos u
imeniku – kombinacija imena i broja. Može se dodati i adresa, ali to
za demonstriranje principa nije neophodno. Takođe, definišemo i
klasu Imenik.
• Osoba.java
• Neophodno je poboljšati klasu Osoba koju smo imali u primeru
Guzva, tako da njeni objekti postanu upotrebljivi kao ključevi u mapi
koju ćemo koristiti – za smeštanje unosa imenika
• Dodaćemo metod equals() i predefinisaćemo default hashCode()
metod.
Imenik
• da implementiramo metod equals() samo pozovemo compareTo()
metod koji smo implementirali za Comparable<> interfejs.
• Možemo dodati još nešto korisno, a to je statički metod koji će
učitavati podatke za Osoba objekat sa tastature
• BrojTelefona.java
• ovde se može izvršavati gomila provera validnosti brojeva, ali to za
ovaj primer nije od značaja
• dodajemo statički metod za učitavanje broja sa tastature
• U praksi bi svakako trebalo proveriti korektnost unosa, ali za prikaz
kako funkcionišu heš-mape, to nije potrebno
• Unos.java
• unos u imeniku kombinuje ime i broj
• Imenik.java
Imenik
• za smeštanje Unos objekata koristimo HashMap<Osoba, Unos>
atribut, imenik.
• Koristićemo Osoba objekat odgovarajući za unos kao ključ, pa
metod dodajUnos() ima samo da pristupi Osoba objektu Unos
objekta koji mu je prosleđen i da ga koristi kao prvi argument
metoda put() za imenik.
• TestImenik.java
• //
• Da bismo učinili program malo interesantnijim, dodajemo metod za
listanje svih unosa iz imenika u abecednom poretku po imenima.
• 1 način da se to uradi bio bi da se kreira povezana lista unosa i
iskoristi sort() metod klase Collections da se oni sortiraju
• metod sort() očekuje argument tipa List<>, gde tip elemenata liste
implementira interfejs Comparable<>. Tako, da bismo mogli da
sortiramo unose u imeniku, klasa Unos mora da implementira
interfejs Comparable<>.
Imenik
• Sada možemo implementirati metod izlistajUnose() u klasi Imenik za
listanje unosa u abecednom poretku.
• to je jednostavno: poziv metoda values() za objekat imenik vraća
objekte iz mape, koji su Unos objekti, kao Collection<>.
• To prosleđujemo konstruktoru klase LinkedList<Unos> da bismo
dobili objekat tog tipa.
• Klasa LinkedList<> implementira interfejs List<>, pa se objekat
unosi može proslediti kao argument metodu sort() na sortiranje.
• Ažurirani main() metod: dodajemo izbor za listanje svih unosa

You might also like