Top Banner
Java-Kolekcije i iteratori (1) Pripremio Atila Rafai Ako ne znate koliko ćete objekata koristiti da biste rešili neki problem ili postoji mogućnost da će im broj biti promenljiv, onda postoji i problem njihovog smeštanja u memoriju. U programskom jeziku Java rešenje tog problema predstavljaju kolekcije - objekti koji se koriste za smeštanje i manipulaciju nedefinisanim brojem objekata. Sve što je potrebno, s gledišta dizajna korisničkog programa, jeste niz objekata koji ima promenljivu veličinu i metode koje omogućavaju da se manipuliše tim objektima. Ali ima dobrih razloga za postojanje više tipova kolekcija. Kao prvo, različite vrste kolekcija treba da imaju različite načine na koji se može manipulisati podacima koje sadrže. Kao drugo, različite vrste kolekcija imaju različite nivoe efikasnosti odrađivanja različitih operacija. (Na primer, neće se na isti način umetnuti novi element u sortiranu ili običnu listu.) Razrađeni objektno orijentisani programski jezici sadrže paket klasa koji služe za rešenje problema kolekcija. U jeziku C++ to je STL (Standard Template Library). Pošto je Java nastala kao jezik koji je trebalo da zameni C++ u određenoj klasi problema, pa joj je jezik C++ bio početni uzor (a ona je kasnije narasla do sadašnjih mogućnosti), i u njoj se nalazi grupa takvih klasa. Java je podržavala kolekcije već od verzije 1.0; ta je podrška unekoliko bila poboljšana u verziji 1.1, ali se tek u verziji 2.0 došlo do obuhvatnije implementacije kolekcija. Osnovne klase koje se koriste za kreiranje kolekcija tretiraju sve objekte koje mogu da sadrže kao da nemaju specifikovan tip, tačnije, bazirane su na pretpostavci da sadrže objekte tipa Object. Pošto sve klase u Javi imaju na početku svoje hijerarhije nasleđivanja 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 rešenje ima dovoljnu širinu. Problem nastaje pri proveri tipa objekta koji neka kolekcija može da sadrži. Ako kreirate neki niz ili matricu promenljivih imate mogućnost provere tipova podataka koje pokušavate da upišete. To znači da za vreme pisanja programa kompajler može proveriti da li ste pogrešili prilikom pisanja programskog koda. U slučaju kolekcija to nije moguće, pa ostaje samo neprijatna mogućnost da korisnik vašeg programa tokom rada dobije poruku o neuspešnom pokušaju izvršenja nelegalne instrukcije od strane JVM-a (Java Virtual Machine), čime se prekida i rad programa. U praksi, pišući 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 znači da ga tretira kao da je primerak neke opštije klase (upcasting), pa se može reći da on gubi svoj identitet. Kasnije koristite te iste objekte, ali prilikom preuzimanja iz kolekcije ne možete sa sigurnošću tvrditi kog su tipa ti objekti. Pre korišćenja ovih objekata ponovo ih vraćate u prvobitni tip (downcasting) kako bi ste mogli da koristite metode svojstvene toj klasi objekata. Ako je oblik (klasa) u koji vraćate objekat pogrešan, dobijate poruku o grešci: 1
19

CET - Java Kolekcije i Iteratori

Nov 21, 2015

Download

Documents

Goran

Java Kolekcije i Iteratori
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
  • 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