You are on page 1of 19

Java-Kolekcije i iteratori (1)

Pripremio Atila Rafai

Ako ne znate koliko ete objekata koristiti da biste reili neki problem ili postoji
mogunost da e im broj biti promenljiv, onda postoji i problem njihovog smetanja
u memoriju. U programskom jeziku Java reenje tog problema predstavljaju
kolekcije - objekti koji se koriste za smetanje i manipulaciju nedefinisanim brojem
objekata.
Sve to je potrebno, s gledita dizajna korisnikog programa, jeste niz objekata koji
ima promenljivu veliinu i metode koje omoguavaju da se manipulie tim
objektima. Ali ima dobrih razloga za postojanje vie tipova kolekcija. Kao prvo,
razliite vrste kolekcija treba da imaju razliite naine na koji se moe manipulisati
podacima koje sadre. Kao drugo, razliite vrste kolekcija imaju razliite nivoe
efikasnosti odraivanja razliitih operacija. (Na primer, nee se na isti nain
umetnuti novi element u sortiranu ili obinu listu.)
Razraeni objektno orijentisani programski jezici sadre paket klasa koji slue za
reenje problema kolekcija. U jeziku C++ to je STL (Standard Template Library).
Poto je Java nastala kao jezik koji je trebalo da zameni C++ u odreenoj klasi
problema, pa joj je jezik C++ bio poetni uzor (a ona je kasnije narasla do
sadanjih mogunosti), i u njoj se nalazi grupa takvih klasa. Java je podravala
kolekcije ve od verzije 1.0; ta je podrka unekoliko bila poboljana u verziji 1.1,
ali se tek u verziji 2.0 dolo do obuhvatnije implementacije kolekcija.
Osnovne klase koje se koriste za kreiranje kolekcija tretiraju sve objekte koje mogu
da sadre kao da nemaju specifikovan tip, tanije, bazirane su na pretpostavci da
sadre objekte tipa Object. Poto sve klase u Javi imaju na poetku svoje
hijerarhije nasleivanja klasu Object, to i ne predstavlja neki problem. Za
primitivne tipove podataka (byte, char, int, float, ...) postoje klase koje se mogu
koristiti umesto njih, tako da ovakvo reenje ima dovoljnu irinu. Problem nastaje
pri proveri tipa objekta koji neka kolekcija moe da sadri. Ako kreirate neki niz ili
matricu promenljivih imate mogunost provere tipova podataka koje pokuavate da
upiete. To znai da za vreme pisanja programa kompajler moe proveriti da li ste
pogreili prilikom pisanja programskog koda. U sluaju kolekcija to nije mogue, pa
ostaje samo neprijatna mogunost da korisnik vaeg programa tokom rada dobije
poruku o neuspenom pokuaju izvrenja nelegalne instrukcije od strane JVM-a
(Java Virtual Machine), ime se prekida i rad programa.
U praksi, piui program vi kreirate svoju kolekciju i punite je po svojim potrebama
nekim svojim objektima. Prilikom upisa objekta u kolekciju ona prima va objekat
kao da je tipa Object, to znai da ga tretira kao da je primerak neke optije klase
(upcasting), pa se moe rei da on gubi svoj identitet. Kasnije koristite te iste
objekte, ali prilikom preuzimanja iz kolekcije ne moete sa sigurnou tvrditi kog su
tipa ti objekti. Pre korienja ovih objekata ponovo ih vraate u prvobitni tip
(downcasting) kako bi ste mogli da koristite metode svojstvene toj klasi objekata.
Ako je oblik (klasa) u koji vraate objekat pogrean, dobijate poruku o greci:

U ovom primeru paprika ne predstavlja problem u sluaju kreiranja torte od


banana. Problem se javlja tek prilikom korienja torte, jer Java prijavljuje greku:

Downcasting nije potreban samo za objekte tipa String, jer kadgod kompajler
oekuje objekat tipa String, a ne dobije takav, on automatski koristi metod
toString(), koji je definisan u klasi Object (i moe se redefinisati), a koji vraa naziv
klase iji je primerak taj objekat, praen znakom '@' i heksadecimalnim oblikom
hash koda tog objekta. U sluaju da elite da iskoristite neto od te kombinacije:

Kolekcije i iteratori (2)


U Javi se framework za kolekcije (skup klasa koje omoguavaju osnovu za dodavanje
naprednije funkcionalnosti) sastoji od vie interfejsa i apstraktnih klasa koje su
kreirane za implementaciju osnovnih metoda i proirivanje osnovnih tehnika, kao to
je tehnika korienja iteratora.

Interfejsi
Postoje dva osnovna interfejsa za kolekcije: Collection i Map. Ti interfejsi definiu
kakav treba da bude interfejs svake kolekcije i time definiu koje osnovne funkcije
treba da postoje.

Interfejsi kolekcija
Interfejs Collection navodi (izmeu ostalih) osnovne metode kojima se zahteva da se
definiu (implementiraju) osnovne operacije koje treba da postoje nad kolekcijom:
dodavanje elementa, provera da li neki element ve postoji u kolekciji, brisanje
elementa iz kolekcije, nain na koji se moe redom pristupiti svim elementima
kolekcije, kao i da se moe proveriti koliko trenutno ima elemenata u nekoj
kolekciji:
boolean add(Object element)
boolean contains(Object element)
boolean remove(Object element)
Iterator iterator()
int size()

Kao proirenja interfejsa Collection postoje kolekcije List i Set, koje postavljaju
dodatne zahteve.
Interfejs List uvodi ureenu kolekciju, pa samim tim postoje i dodatni zahtevi da se
element moe uneti na odgovarajuu poziciju unutar postojee liste, preuzeti na
osnovu indeksa u toj listi ili obrisati element koji ima dati indeks:
void add(int index, Object element)

Object get(int index)


void remove(int index)

Za kretanje napred/nazad kroz listu ovaj interfejs koristi drugi interfejs,


ListIterator, koji predstavlja proirenje interfejsa Iterator.
Interfejs Set je veoma slian interfejsu Collections; on navodi samo jedno dodatno
ogranienje: ne moe biti duplikata unutar seta objekata. Zbog toga se moraju
definisati dve dodatne metode, equals i hashCode, koje treba da omogue testiranje
dva objekta na jednakost i kreiranje he koda koji e omoguiti da objekti koji
imaju istu vrednost ne budu proglaeni za razliite, to je osnovna postavka jo iz
klase Object (tu se koristi adresa objekta kao he kod).
Kod interfejsa Map postoji neto drugaiji zahtev: mora postojati mogunost
kreiranja "renika" koji e imati parove klju/vrednost. Preciznije reeno, mora
postojati mogunost da se neki unos u mapu moe krae opisati, ime se omoguava
bra pretraga mape:
Object put(Object key, Object value)
boolean containsKey(Object key)
boolean containsValue(Object value)
Object get(Object key)
Object remove(Object key)
int size()

Na primer, moete kreirati adresar koji e sadrati detaljne podatke o osobama s


kojima ste u kontaktu, ali ete taj adresar pretraivati samo na osnovu prezimena i
imena, umesto da gubite vreme u pretraivanju daleko vee koliine podataka.

Osnovne klase
Da bi se programerima olakao rad, postoji pet apstraktnih klasa koje implementiraju
one metode koje predstavljaju samu osnovu:
AbstractCollection
AbstractList
AbstractSequentialList
AbstractSet
AbstractMap

Ako elite da implementirate svoju kolekciju, verovatno ete iskoristiti neku od


ovih klasa. Pored te mogunosti, Java u biblioteci ima est konkretnih klasa:
LinkedList
ArrayList
HashSet
TreeSet
HashMap
TreeMap

Ako smatrate da vam to nije dovoljno, postoje i klase koje su postojale u Java
biblioteci i pre nastanka ovog naprednijeg sistema interfejsa i klasa (engl. legacy
container classes), a koje na svoj nain prilaze problemu kolekcija, pa su, sa nekim
modifikacijama, ukljuene i u ovaj napredniji sistem:
Vector
Stack
Hashtable
Properties

Iteratori
Sve kolekcije imaju neki nain da prime neki objekat ili da predaju svoj sadraj.
Uvek postoje metode push ili add, ili neke druge slinog naziva. U skladu s tim da
uvek postoji mogunost da se u toku projektovanja programa doe do nekog reenja
koje zahteva promenu vrste kolekcije, kreiran je i univerzalan nain pristupa
elementima kolekcije - iterator. To je objekt ija je namena omoguavanje
sekvencijalnog pristupa elementima.
Na samom startu Java je imala standardni iterator, Enumeration, koji su koristile sve
klase kolekcije. U verziji 2 Jave dodata je kompleksnija biblioteka kolekcija, pa je
zbog novih potreba dodat i nov iterator, Iterator, koji ima dodatne osobine (mogue
je brisanje elemenata preko iteratora) i krae nazive za metode koje se koriste za
pristup elementima.
Iterator interfejs ima dva metoda koji su analogni onima u interfejsu Enumerator; to
su hasNext (za stariju verziju hasMoreElements) i next (analogno sa nextElement u
Enumeratoru). Na primer, ukoliko bi imali neku kolekciju koja bi sadrala podatke za
sve zaposlene u jednom preduzeu, preuzimanje tih podataka bi izgledalo kao:
Iterator i = zaposleni.iterator();
while (i.hasNext())
{
Zaposleni z = (Zaposleni)i.next();
...
}

Povremeno ete naii na neki metod koji vue korene jo iz verzije 1.0, a koji
oekuje, kao parametar, enumerator. Statika metoda Collections.enumeration koristi se
za kreiranje enumerator objekta za datu kolekciju. Na primer:
// niz ulaznih tokova
ArrayList streams = ...;
// konstruktor SequenceInputStream objekta kao parametar prima enumerator
SequenceInputStream in = new SequenceInputStream(
Collections.enumeration(streams)
);

Kao primer korienja iteratora uzeemo prepravljen primer iz prethodnog dela:


// program torta.java
import java.util.*;
class Banana { }
public class torta
{
public static void main(String[] args)

ArrayList tortaOdBanana = new ArrayList();


for (int i = 0; i < 7; i++)
tortaOdBanana.add(new Banana());
Iterator i = tortaOdBanana.iterator();
while (i.hasNext())
System.out.println(((Banana)i.next()).toString());

Moete videti da se jedina bitna izmena nalazi samo u nekoliko poslednjih linija.
Umesto da koristite liniju
for (int i = 0; i < tortaOdBanana.size(); i++)
System.out.println(((Banana)tortaOdBanana.get(i)).toString());

koristi se Iterator za prolazak kroz niz elemenata


Iterator i = tortaOdBanana.iterator();
while(i.hasNext())
System.out.println(((Banana)i.next()).toString());

Korienjem iteratora programer ne mora da vodi rauna o broju elemenata u


kolekciji. To se postie metodama hasNext() i next().
java.util.Enumeration
boolean hasMoreElements()
Object nextElement()

Vraa true ako ima jo nepregledanih elemenata u kolekciji.


Vraa sledei element kolekcije. Greka je ako se pozove
ovaj metod, a da je pre toga hasMoreElements() vratio false.

java.util.Iterator
boolean hasNext()
Object next()
void remove()

Vraa true ako ima jo nepregledanih elemenata u kolekciji.


Vraa sledei element kolekcije.
Uklanja iz kolekcije poslednji lan koji je bio vraen metodom
next().

Korienje listi
List

ArrayList

Redosled je najvanija osobina interfejsa List; osigurava se odravanje


elemenata u odreenom redosledu. List dodaje nekoliko metoda interfejsu
Collection, koje omoguavaju umetanje i brisanje elemenata iz sredine
liste. (Ovo se preporuuje samo za klasu LinkedList) Interfejs List
omoguava kreiranje ListIterator objekta koji omoguava kretanje kroz
listu u oba smera, kao i umetanje i brisanje elemenata iz sredine liste
(preporuuje se samo za LinkedList).
Implementacija interfejsa List koja sadri niz Object[] koji dinamiki
realocira. Koristi se umesto prethodne varijante (Vector) kao kolekcija
za opte potrebe. Omoguava brz pristup elementima, ali je sporo umetanje
4

i brisanje elemenata iz sredine liste. ListIterator koristi se samo za


prolazak kroz listu, ali ne i za umetanje i brisanje elemenata, jer je za
to pogodnije koristiti drugu kolekciju - LinkedList.
LinkedList Prua optimalan sekvencijalni pristup i najoptimalnija umetanja i
brisanja iz sredine liste. Relativno spora implementacija za direktni
pristup nekom elementu liste koji nije naredni, za ta je pogodnije
koristiti ArrayList. Takoe poseduje metode addFirst, addLast, getFirst,
getLast, removeFirst, i removeLast (koje nisu definisane ni u jednom
interfejsu ili osnovnoj klasi), to omoguava korienje objekta tipa
LinkedList, kao da je stek ili red.
ArrayList i Vector
Klase ArrayList i Vector koriste se kao kolekcije opte namene. Obe klase imaju neki
svoj inicijalni kapacitet koji se poveava svaki put kada treba dodati novi element
u ve popunjenu kolekciju. Operacija poveanja kapaciteta vrlo je zahtevna, jer se
kreira nova kolekcija koja sadri sve elemente koji ve postoje u datoj kolekciji, s
tim da se ostavlja prazan prostor za eventualne nove elemente liste, a postojea
kolekcija se potom brie iz memorije. Kod klase Vector programer ima veu slobodu
prilikom rada, jer moe specifikovati inkrement veliine kolekcije u sluaju
popunjenosti liste.
Sve metode klase Vector su sinhronizovane, to znai da moete pristupiti objektu
tipa Vector iz dve ili vie niti. To znai mnogo kad je u pitanju sigurnost
podataka, ali u veini sluajeva je ta mogunost potpuno nepotrebna, a u domenu
brzine izvravanja programa predstavlja samo usporenje. Suprotno metodama te klase,
ArrayList metode nisu sinhronizovane, pa su samim tim i bre.
Korienje klase ArrayList umesto klase Vector vrlo je jednostavno, jer je jedino
potrebno koristiti metode kraih naziva, get i set, umesto metoda elementAt i
setElementAt klase Vector.
LinkedList
Za razliku od ArrayList klase, klasa LinkedList, iako ima metod get, nema nikakav
nain da optimizuje vreme sekvencijalnog pristupa elementima tom metodom. Sledei
programski kd je najsporiji nain sekvencijalnog pristupa elementima LinkedList
objekta:
for (int i = 0; i < list.size(); i++)
// uradi neku operaciju koristeci list.get(i);

Svaki put kada se pristupa sledeem elementu (po indeksu) potraga poinje od poetka
liste, jer LinkedList klasa nije kreirana s namerom da se zapamti trenutna pozicija
unutar liste. Ono to jedino ini neku optimizaciju jeste injenica da, ako je
indeks vei od polovine broja elemenata u listi, pretraga poinje s kraja liste.

Korienjem iteratora ListIterator, razlika u brzini prolaska kroz listu je vrlo


oigledna. Iterator vodi rauna o trenutnoj poziciji. Tanije reeno, Java iteratori
u sutini pokazuju izmeu1 dva elementa: nextIndex metod vraa indeks elementa koji
bi bio dostupan narednim pozivom metode next; metod previousIndex vraa indeks
elementa koji bi bio dostupan narednim pozivom metode previous.
Jedini razlog korienja povezane liste jeste minimizacija korienog vremena i
resursa za operacije umetanja i brisanja elemenata iz sredine liste. U sluaju da je
broj elemenata mali, bolje je koristiti objekat tipa ArrayList.

Dvostruko povezana lista

Dodavanje elementa u povezanu listu

???

Uklanjanje elementa iz povezane liste

java.util.Vector
Enumeration elements()

Vraa Enumeration objekat za prolazak kroz


elemente vektora.

java.util.List
ListIterator listIterator()
ListIterator listIterator(int index)

void add(int i, Object element)


void addAll(int i, Collection elements)

Object remove(int i)

Object set(int i, Object element)


int indexOf(Object element)

Vraa ListIterator za prolazak kroz listu.


Vraa ListIterator za prolazak kroz listu, pri
emu e prvi poziv metode next dati element
koji ima indeks kao onaj naveden kao
parametar ove metode.
Dodaje element na specifikovanu poziciju.
Dodaje sve elemente iz kolekcije u listu, a
indeks prvog elementa koji treba da se unese
bie i (ostali slede iza).
Brie element iz liste (sa navedenim
indeksom) i daje taj element kao povratnu
vrednost.
Zamenjuje postojei element sa datim indeksom
novim elementom.
Vraa indeks prvog objekta u listi, koji je
jednak objektu datom kao parametar, a ako
nema takvog elementa, vraa -1.

java.util.ListIterator
void add(Object element)
void set(Object element)

Dodaje element ispred trenutne pozicije.


Zamenjuje poslednji element kome je bilo pristupljeno sa
next ili previous. Kreira izuzetak IllegalStateException ako
je struktura liste bila modifikovana po poslednjem pozivu
7

boolean hasPrevious()
Object previous()
int nextIndex()
int previousIndex()

metoda next ili previous.


Vraa true ako postoji makar jo jedan element do kraja
liste.
Vraa prethodni element. Kreira izuzetak
NoSuchElementException ako je dostignut poetak liste.
Vraa indeks elementa koji bi bio vraen sledeim pozivom
metode next.
Vraa indeks elementa koji bi bio vraen sledeim pozivom
metode previous.

java.util.LinkedList
LinkedList()
LinkedList(Collection elements)
void addFirst(Object element)
void addLast(Object element)
Object getFirst()
Object getLast()
Object removeFirst()
Object removeLast()

Kreira praznu listu.


Kreira listu i dodaje sve elemente iz kolekcije.
Dodaje element na poetak liste.
Dodaje element na kraj liste.
Vraa element sa poetka liste.
Vraa element sa kraja liste.
Skida iz liste2 i vraa element koji je na poetku
liste.
Skida iz liste3 i vraa element koji je na kraju
liste.

Atila Rafai

2
3

ta? element?
ta? element?

Kolekcije i iteratori (3)


Korienje setova
Interfejs Set ima iste metode kao i Collection, tanije, ne dodaje nove metode, ve
je ponaanje nekih metoda malo drugaije: Set ne dozvoljava duplikate. Ako ste neki
objekat postavili u Set, ne moete dodati jo jedan objekat koji ima iste vrednosti.

He tabele
Liste omoguavaju programeru da odredi u kom redosledu e se nalaziti objekti unutar
tih kolekcija. Ali ako se trai element kome se ne zna tana pozicija, moraju se
redom pregledavati elementi dok se ne naie ba taj element. Ako je kolekcija
velika, to moe biti vremenski vrlo zahtevna operacija.
S druge strane, ako nije bitno kako se zapisuju elementi u kolekciju, ve je bitna
samo brzina pristupa, tada se koriste druge vrste kolekcija - he tabele. He tabela
izraunava celobrojnu vrednost zvanu he kd, koju pridruuje pojedinanom elementu
kolekcije. Za he kd je bitno da se moe brzo izraunati i da zavisi samo od
elementa za koji se kreira, a ne i od ostalih elemenata u he tabeli.
He tabela je implementirana kao niz ulananih lista zvanih bucket (u bukvalnom
prevodu 'vedro'). Da bi se naao objekat u he tabeli, potrebno je izraunati njegov
he kod, i korienjem ukupnog broja bucketa se, preko modula, nalazi indeks bucketa
u kome se nalazi taj element. Kada se, tim brzim putem, "zahvati" vei broj
elemenata (kao voda vedrom), naredna operacija je redno pregledavanje elemenata u
listi da bi se pronaao odgovarajui element. Na primer, ako objekat ima he kod
531, a ima 101 bucket, tada e on biti u 26-om, jer je ostatak od celobrojnog
deljenja 531 sa 101 ba 26.
Moe se desiti da je u bucketu samo taj element, ali se moe desiti i da je tamo jo
neki; tada se kae da je dolo do he kolizije. U tom sluaju mora se pristupiti
poreenju sa moda i svim elementima koji se nalaze u bucketu. Ako je algoritam he
koda dovoljno dobar, a broj bucketa dovoljno veliki, tada je neophodno da se izvri
samo nekoliko poreenja, pa je samim tim potrebno malo vremena da se doe do
eljenog elementa tabele. Ako je pak tabela vie popunjena, poveava se broj
kolizija, a samim tim se usporava rad nad he tabelom.
Ako je poznato koliko e biti otprilike elemenata u tabeli, moe se specifikovati
inicijalni kapacitet. Ta vrednost bi trebalo da bude otprilike 150% broja elemenata.
Poto se po nekim ispitivanjima pokazalo da je za inicijalni broj bucketa poeljno
odabrati prost broj (onaj koji je deljiv samo sa samim sobom), ako imamo potrebu za
oko 100 ulaza, inicijalna veliina bi trebalo da bude 151 bucket.

Ako se eli vea kontrola nad performansama he tabele, dolazi do problema u sluaju
kada inicijalni kapacitet nije mogue odrediti. Tada se, ako se poetni kapacitet
postavi na nedovoljnu vrednost, javlja potreba za tzv. rehashing operacijom, tj.
kreiranjem nove tabele sa odgovarajuom veliinom, njenim punjenjem objektimavrednostima iz 'stare' tabele i brisanjem iz memorije stare tabele. To je vremenski
zahtevna operacija, pa se mora uvesti kompromisno reenje: specifikuje se load
factor. U programskom jeziku Java to je vrednost kojom se odreuje kolika treba da
bude iskorienost bucketa da bi se pristupilo reheiranju. Na primer, ako load
factor ima vrednost 0.75 (to je u Javi podrazumevana vrednost), tabela se
automatski reheira na duplo veu tabelu kada ukupni broj iskorienih bucketa pree
75% inicijalne vrednosti.

HashSet
HashSet je klasa iz Java biblioteke kolekcija koja implementira set pomou he
tabele. Njen podrazumevani konstruktor kreira he tabelu koja ima inicijalno 101
bucket, a load factor je 0.75. Set je kolekcija elemenata bez duplikata, tako da se
metod add() koristi za pokuaj upisa novog objekta. Metod contains() redefinisan je
tako da na postojanje odreenog elementa proverava samo odgovarajui bucket.
Iterator he seta postoji za prolazak kroz celu tabelu.
Navedeni program, koji je primer korienja he seta, uitava sve rei sa svog
ulaza, upisuje ih u he tabelu i, na kraju, ispisuje niz preuzetih rei te ukupan
broj naenih rei:
import java.util.*;
import java.io.*;
public class HashSetTest
{
public static void main(String[] args)
{
Set words = new HashSet();
try
{
BufferedReader in =
new BufferedReader(
new InputStreamReader(
new FileInputStream(
new File("HashSetTest.java")
)));
String line;
while ((line = in.readLine()) != null)
{
StringTokenizer tokenizer =
new StringTokenizer(line);
while (tokenizer.hasMoreTokens())
{
String word = tokenizer.nextToken();
words.add(word);
}
}
}

catch (IOException e)
{
System.out.println("Error " + e);
}
Iterator iter = words.iterator();
while (iter.hasNext())
System.out.println(iter.next());
System.out.println(words.size() + " razlicitih reci.");

U ovom primeru su se mogli koristiti objekti tipa String, jer ta klasa ima hashCode()
metod koji rauna he kd za niz znakova. U sluaju objekta String to je integer
koji je izveden iz samih znakova.
U sluaju ostalih klasa, kompajler nee prijaviti greku ako upisujete neki objekat
u he tabelu, a da niste definisali metodu hashCode(), i to zato to je ta metoda
definisana u klasi Object. Problem s ovom implementacijom metode predstavlja to to
ona vrednost tipa integer (za koju je dozvoljeno da je negativna) izvodi iz
memorijske adrese objekta. To znai da u optem sluaju ova metoda za svaki objekat
daje razliit he kod, nezavisno od toga da li su vrednosti neka dva objekta
identine. Zbog toga je potrebno redefinisati metod za svaku klasu koja bi mogla
biti ikada ubaena u he tabelu. Pri tome treba voditi rauna da to odslikava
sadraj objekta. Na primer, u sluaju da se u he tabelu ubacuju objekti koji
predstavljaju zaposlene u nekoj firmi, treba koristiti ili njihovu ifru u bazi
podataka te firme ili njihov matini broj.
Osim redefinisanja metode hashCode() potrebno je redefinisati i metodu equals(). Ta
metoda je takoe definisana u klasi Object, ali ni ta implementacija ne odgovara
ovom zahtevu.
Napomena: potrebno je uskladiti te dve metode ako poziv metode x.equals(y) vrati
true, tada i vrednosti koje se dobijaju pozivima metoda x.hashCode() i y.hashCode()
moraju biti jednake.

TreeSet
Ova klasa je slina HashSet klasi, osim to ima dodato jedno poboljanje: objekti su
sortirani. Svaki put kada se neki element doda u ovu kolekciju, postavlja se na
odgovarajue mesto. Podrazumeva se da objekat koji se postavlja u kolekciju ima
implementiran interfejs Comparable. Taj interfejs deklarie samo jedan metod:
int compareTo(Object other)
Poziv metode x.compareTo(y) mora vratiti 0 ako su dva objekta jednaka, negativnu
vrednost ako je x pre objekta y u tom nainu sortiranja, a pozitivnu vrednost u

suprotnom sluaju. Tane vrednosti nisu bitne, bitan je samo znak vrednosti koja se
vraa pozivom te metode. Java implementira tu metodu za neke svoje osnovne klase,
tako da klasa String ima implementiranu metodu compareTo(), a za sortiranje se
koristi takozvani leksikografski redosled, tj. redosled po azbunom rasporedu slova.

Postoji jedan problem u implementiranju te metode. U sluaju da se neki objekti,


shodno razliitim situacijama, ele sortirati po razliitim kriterijumima, ovakvim
pristupom problemu to nije mogue. Zbog ovakvih problema, a i zbog verovatnoe da
neka klasa nema implementiranu metodu compateTo(), mogue je koristiti konstruktor
klase TreeSet koji kao parametar prima objekat tipa Comparator. Interfejs Comparator
ima deklarisanu samo jednu metodu:
int compare(Object x, Object y)
Kao i metoda compareTo(), ova metoda treba da vraa nulu, negativnu ili pozitivnu

celobrojnu vrednost, u zavisnosti od vrednosti objekata koje prima kao parametre.


Dodavanje nekog objekta u ovakvu kolekciju neto je sporije od dodavanja u HashSet
objekat, ali je jo uvek bre od dodavanja elementa u sredinu nekog niza ili
ulanane liste. Na primer, ako ovakva kolekcija ima n elemenata, tada je proseno
potrebno log2n poreenja da bi se nala odgovarajua pozicija za novi element. Na
primer, ako je u kolekciji 1.000 elemenata, za dodavanje novog elementa je potrebno
oko 10 poreenja.
Ovo je preraen prethodni primer, tako da kao izlaz daje sortirani niz tokena koje
prima kao ulaz. Ovde je prikazana implementacija korienja objekta tipa Comparator
da bi se naveo svoj redosled sortiranja:
import java.util.*;
import java.io.*;
public class TreeSetTest
{
public static void main(String[] args)
{
Set words = new TreeSet(new Comparator()
{
public int compare(Object x, Object y)
{
String sx = (String) x;
String sy = (String) y;
int z = sx.compareToIgnoreCase(sy);
return -z;
}
}
}
);
try
{
BufferedReader in =
new BufferedReader(
new InputStreamReader(
new FileInputStream(
new File("TreeSetTest.java")
)));
String line;
while ((line = in.readLine()) != null)
{
StringTokenizer tokenizer =
new StringTokenizer(line);
while (tokenizer.hasMoreTokens())
{
String word = tokenizer.nextToken();
words.add(word);

}
catch (IOException e)
{
System.out.println("Error " + e);
}
Iterator iter = words.iterator();
while (iter.hasNext())
System.out.println(iter.next());
System.out.println(words.size() + " razlicitih reci.");

Pripremio Atila Rafai

Kolekcije i iteratori (4)


Korienje mapa
Apstraktna klasa Dictionary bila je prva implementacija (jo iz JDK verzije 1.0) neke klase koja je imala parove objekata
klju-vrednost. Zbog te osobine je i dobila naziv renik (engl. dictionary). Klasa HashTable bila je jedina implementacija te
klase, a dodavala je he tabelu kao nain zapisivanja parova klju-vrednost. Od JDK verzije 1.1 poelo se s poboljanjima
podrke za kolekcije, ali je tek od verzije 1.2 napravljen radikalan zaokret.
Umesto klase Dictionary, koja je bila potpuno apstraktna, koriste se: interfejs Map, podinterfejs SortedMap, i klase koje
implementiraju ovaj interfejs - AbstractMap, HashMap, Hashtable, RenderingHints, WeakHashMap, i Attributes.
Klase HashMap i TreeMap (izvedena iz AbstractMap) predstavljaju klase opte upotrebe.

Interfejs Map
Konceptualno, ovaj interfejs definie kolekciju koja je slina vektoru, ali se kolekcija pretrauje, umesto po indeksima, po
nekom objektu. Na primer, ako imate renik koji treba da sadri strune izraze, kreirate mapu i ona za svaku re, koja je
objekat, sadri takoe neki odgovarajui objekat. Pri tome vai pravilo da se svi struni izrazi pojavljuju samo jednom u
reniku i da oni predstavljaju klju po kome se nalazi objekat koji predstavlja neku vrednost, to bi u ovom sluaju bilo
objanjenje izraza.
Za reenje ovakvog problema koristi se ba klasa izvedena iz interfejsa Map. Ovaj interfejs podrava postojanje parova
objekata klju-vrednost, pri emu ne moe postojati duplikat kljueva. Metode koje deklarie Map jesu: size() daje
informaciju o broju elemenata u mapi, isEmpty() vraa true ako mapa nema niti jedan element, put(Object key,
Object value) dodaje specificiranu vrednost u mapu pod specifikovanim kljuem, get(Object key) vraa vrednost
pridruenu datom kljuu, remove(Object key) uklanja par klju-vrednost za datu vrednost kljua key. Interfejs Map
takoe zahteva dva iteratora: keys() za kljueve i elements() za vrednosti.
Evo primera kako treba da izgleda klasa koja se izvodi iz klase AbstractMap:
import java.util.*;
public class TestMap extends AbstractMap
{
private ArrayList keys = new ArrayList();
private ArrayList values = new ArrayList();
public int size() { return keys.size(); }
public boolean isEmpty() { return keys.isEmpty(); }
public Object put(Object key, Object value)
{
int index = keys.indexOf(key);
if (index == -1) // nema ga u tabeli
{
keys.add(key);
values.add(value);
return null;
}
else // postoji - zameni ga
{
Object returnval = values.get(index);
values.set(index, value);
return returnval;

}
}
public Object get(Object key)
{
int index = keys.indexOf(key);
if (index == -1) return null;
return values.get(index);
}
public Object remove(Object key)
{
int index = keys.indexOf(key);
if (index == -1) return null;
keys.remove(index);
Object returnval = values.get(index);
values.remove(index);
return returnval;
}
public Set keySet() { return new HashSet(keys); }
public Collection values() { return values; }
public Set entrySet() { return new HashSet(values); }

Metod put() proverava prvo da li ve postoji odreeni klju u mapi. U sluaju da postoji, zamenjuje staru vrednost novom,
dok staru vrednost vraa kao povratnu vrednost. Time se spreava da se preko neke ve postojee vrednosti upie nova, a da
se stara vrednost izgubi. Ovaj metod se moe modifikovati tako da spreava zamenu postojee vrednosti, to bi moglo
odgovarati ako se korienjem ovog primera implementira renik.
Metod remove() takoe vraa objekat-vrednost (u sluaju uspenog brisanja, tako da se moe skratiti postupak preuzimanja
objekata-vrednosti iz mape nije potrebno prvo preuzeti neku vrednost korienjem metode get(), pa je naknadno brisati,
ve za to moe direktno posluiti metod remove()).
java.util.Map (interesantniji metodi):
Object get(Object key)
Object put(Object key, Object
value)

void putAll(Map entries)


boolean containsKey(Object key)
boolean containsValue(Object value)
Set entrySet()

Set keySet()
Collection values()

Vraa vrednost pridruenu kljuu. U sluaju da klju nije naen u mapi


vraa se null. Kao parametar moe se dati vrednost null.
Postavlja par klju-vrednost u mapu. Ako je klju ve prisutan u mapi, novi
objekat value zamenjuje onaj koji je prethodno bio povezan s tim
kljuem. Ovaj metod vraa staru vrednost za dati klju ili null ako klju
nije bio upisan u mapu. Kao parametre prima klju koji moe biti null i
vrednost koja ne moe biti null.
Dodaje sve ulaze iz mape koja je predata kao parametar.
Vraa true ako dati klju postoji u mapi.
Vraa true ako data vrednost postoji u mapi.
Vraa objekat tipa Set sastavljen od objekata tipa Map.Entry koji sadre
parove klju-vrednost, ali uz neka ogranienja: mogu se uklanjati parovi iz
seta, i to se odraava na mapu, ali se ne mogu dodavati novi parovi kljuvrednost.
Ima identinu funkciju kao i prethodna metoda, samo to kljueve prisutne
u mapi1.
Vraa kolekciju koja sadri samo vrednosti mape. I za ovu kolekciju vae
ogranienja iz prethodna dva metoda.

ta samo to kljueve prisutne u mapi?

java.util.Map.Entry (interesaniji metodi):


Object getKey()
Object getValue()
Object setValue(Object value)

Vraa klju ovog para.


Vraa vrednost ovog para.
Menja postojeu vrednost pridruenu ovom paru, a vraa vrednost koja je pre
ovoga bila u paru.

HashMap
Standardna Java biblioteka klasa sadri dva razliita tipa mapa, HashMap i TreeMap. Obe klase imaju isti interfejs, ali su
razliite po pitanju efikasnosti. Ako je potrebno esto koristiti metod get(), klasa HashMap omoguava brz pristup
elementima, jer koristi he tabelu. Na taj nain, brzim proraunom he koda moe se u veini sluajeva direktno pristupiti
traenom elementu. Ako se taj nain realizacije metoda get() uporedi sa implementacijom istoimene metode u klasi
ArrayList, razlika u brzini e biti vrlo oigledna.
Primer korienja klase HashMap:
import java.util.*;
class Counter
{
int i = 1;
public String toString() { return Integer.toString(i); }
}
class HashMapTest
{
public static void main(String[] args)
{
int iterations = Integer.parseInt(args[0]);
int distribution = Integer.parseInt(args[1]);
HashMap hm = new HashMap();
long time = System.currentTimeMillis();
for (int i = 0; i < iterations; i++)
{
Integer key = new Integer((int)(Math.random() * distribution));
if (hm.containsKey(key))
((Counter)hm.get(key)).i++;
else
hm.put(key, new Counter());
}
System.out.print("Obrada trajala ");
System.out.print(System.currentTimeMillis() - time);
System.out.println(" milisekundi");
System.out.println(hm);
}
}

U ovom primeru klasa HashMap koristi se za proveru ravnomernosti rasporeda sluajnih brojeva koji se dobijaju korienjem
statike metode random() klase Math. Kao parametre, ovaj primer prima dva broja: broj iteracija i raspon u kome e se
proveravati distribucija brojeva. Na kraju modifikacije objekta klase HashMap dobija se i izvetaj o vremenskom trajanju
radnog dela programa (proteklo vreme u milisekundama). Jedan od test rezultata imao je sledee vrednosti:
D:\Java apps>java HashMapTest 1000000 8
Obrada trajala 2090 milisekundi
{7=125046, 6=124756, 5=125079, 4=124639, 3=125280, 2=125047, 1=124916, 0=125237}

Za upotrebu klase HashMap moraju se koristiti i za klju i za vrednost objekti koji imaju odgovarajue definisane i usklaene
metode hashCode() i equals(). One se koriste za proveru jednakosti prilikom upisa u kolekciju. U navedenom primeru to
nije bio sluaj, jer su se koristili objekti tipa Integer. U sluaju da logika programa zahteva korisniki definisane klase za
klju ili vrednost, ovaj zahtev se mora potovati, jer bi u suprotnom program koristio istoimene metode klase Object, koje
koriste adresu objekta kao he kod, zbog ega je svaki objekat identian jedino samome sebi.
java.util.HashMap (interesaniji metodi):
HashMap()
HashMap(Map entries)
HashMap(int initialCapacity)
HashMap(int initialCapacity, float loadFactor)

Konstruie praznu mapu.


Konstruie mapu sa datim inicijalnim podacima.
Konstruie praznu mapu sa datim inicijalnim kapacitetom.
Konstruie praznu mapu sa datim inicijalnim kapacitetom i
faktorom popunjenosti (podrazumevana vrednost iznosi
0.75).

TreeMap
TreeMap predstavlja implementaciju interfejsa Map, koja sadri sortirane parove klju-vrednost. A kako e ti parovi biti
sortirani, to moe biti odreeno prilikom kreiranja objekta klase TreeMap, ako se kao parametar preda objekat tipa
Comparator. Ako se ne koristi taj konstruktor, sortirae se prema prirodnom redosledu, to bi za objekte tipa String
znailo sortiranje po metodu compareTo() iz interfejsa Comparable.
TreeMap predstavlja jedinu klasu koja ima implementiran metod subMap(), koji vraa deo sortirane mape. Ipak, uz sve to,
postoji i loa strana smanjena brzina pristupa elementima. Prethodni primer, ako se umesto HashMap klase koristi klasa
TreeMap, ima sledee rezultate:
D:\Java apps>java TreeMapTest 1000000 8
Obrada trajala 2750 milisekundi
{0=124642, 1=125603, 2=124796, 3=124942, 4=124985, 5=124780, 6=125052, 7=125200}

Evo nekih uporednih rezulta izlaza iz prethodnog primera, ako se za argument iterations odredi vrednost 1.000.000, dok
se vrednost za argument distribution menja sledeim redosledom:
5
50
500
5000
TreeMap
2740
4230
6320
10490
HashMap
2310
2360
2360
3350
Atila Rafai

You might also like