-
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:
1
-
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:
2
-
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)
1
-
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
2
-
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)
3
-
{ 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() Vraa true ako ima
jo nepregledanih elemenata u kolekciji.Object nextElement() Vraa
sledei element kolekcije. Greka je ako se pozove
ovaj metod, a da je pre toga hasMoreElements() vratio false.
java.util.Iterator boolean hasNext() Vraa true ako ima jo
nepregledanih elemenata u kolekciji. Object next() Vraa sledei
element kolekcije. void remove() Uklanja iz kolekcije poslednji lan
koji je bio vraen metodom
next(). Korienje listi List 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).
ArrayList 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.
5
-
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
1 ???
6
-
Uklanjanje elementa iz povezane liste java.util.Vector
Enumeration elements() Vraa Enumeration objekat za prolazak
kroz
elemente vektora. java.util.List ListIterator listIterator()
Vraa ListIterator za prolazak kroz listu. ListIterator
listIterator(int index) 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.
void add(int i, Object element) Dodaje element na specifikovanu
poziciju. void addAll(int i, Collection elements) Dodaje sve
elemente iz kolekcije u listu, a
indeks prvog elementa koji treba da se unese bie i (ostali slede
iza).
Object remove(int i) Brie element iz liste (sa navedenim
indeksom) i daje taj element kao povratnu vrednost.
Object set(int i, Object element) Zamenjuje postojei element sa
datim indeksom novim elementom.
int indexOf(Object element) 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) Dodaje element
ispred trenutne pozicije. void set(Object element) Zamenjuje
poslednji element kome je bilo pristupljeno sa
next ili previous. Kreira izuzetak IllegalStateException ako je
struktura liste bila modifikovana po poslednjem pozivu
7
-
metoda next ili previous. boolean hasPrevious() Vraa true ako
postoji makar jo jedan element do kraja
liste. Object previous() Vraa prethodni element. Kreira
izuzetak
NoSuchElementException ako je dostignut poetak liste. int
nextIndex() Vraa indeks elementa koji bi bio vraen sledeim
pozivom
metode next. int previousIndex() Vraa indeks elementa koji bi
bio vraen sledeim pozivom
metode previous. java.util.LinkedList LinkedList() Kreira praznu
listu. LinkedList(Collection elements) Kreira listu i dodaje sve
elemente iz kolekcije. void addFirst(Object element) Dodaje element
na poetak liste. void addLast(Object element) Dodaje element na
kraj liste. Object getFirst() Vraa element sa poetka liste. Object
getLast() Vraa element sa kraja liste. Object removeFirst() Skida
iz liste2 i vraa element koji je na poetku
liste. Object removeLast() Skida iz liste3 i vraa element koji
je na kraju
liste. Atila Rafai
2 ta? element? 3 ta? element?
8
-
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
objektima-vrednostima 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) Vraa
vrednost pridruenu kljuu. U sluaju da klju nije naen u mapi
vraa se null. Kao parametar moe se dati vrednost null. Object
put(Object key, Object value)
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.
void putAll(Map entries) Dodaje sve ulaze iz mape koja je
predata kao parametar. boolean containsKey(Object key) Vraa true
ako dati klju postoji u mapi. boolean containsValue(Object value)
Vraa true ako data vrednost postoji u mapi. Set entrySet() 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 klju-vrednost.
Set keySet() Ima identinu funkciju kao i prethodna metoda, samo
to kljueve prisutne u mapi1.
Collection values() Vraa kolekciju koja sadri samo vrednosti
mape. I za ovu kolekciju vae ogranienja iz prethodna dva
metoda.
1 ta samo to kljueve prisutne u mapi?
-
java.util.Map.Entry (interesaniji metodi): Object getKey() Vraa
klju ovog para. Object getValue() Vraa vrednost ovog para. Object
setValue(Object value) 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() Konstruie praznu
mapu. HashMap(Map entries) Konstruie mapu sa datim inicijalnim
podacima. HashMap(int initialCapacity) Konstruie praznu mapu sa
datim inicijalnim kapacitetom. HashMap(int initialCapacity, float
loadFactor) 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
Java-Kolekcije i iteratori (1)Java-Kolekcije i iteratori
(2)Kolekcije i iteratori (2)InterfejsiOsnovne
klaseIteratorijava.util.Enumerationjava.util.Iterator
Korienje listiArrayList i
VectorLinkedListjava.util.Vectorjava.util.Listjava.util.ListIteratorjava.util.LinkedListAtila
Rafai
Java-Kolekcije i iteratori (3)Kolekcije i iteratori (3)Korienje
setovaHe tabeleHashSetTreeSet
Java-Kolekcije i iteratori (4)Kolekcije i iteratori (4)Korienje
mapaInterfejs Mapjava.util.Map (interesantniji
metodi):java.util.Map.Entry (interesaniji metodi):
HashMapjava.util.HashMap (interesaniji metodi):
TreeMap