Objektno orijentisano programiranje Java generiki tipovi Izuzeci nastavak
Objektno orijentisano programiranje Java, generički tipovi
Izuzeci, nastavak • Većina predefinisanih klasa izuzetaka u Javi ne dodaje dalje informacije o uslovima koji su kreirali izuzetak • Ovaj generalni nedostatak je iz razloga što te informacije u većini slučajeva mogu da se prikupe jedino predznanjem o izračunavanjima koja su vršena kada se desio izuzetak, a jedini ko zna o tome je onaj ko piše program • Ako nam je potrebno više informacija o okolnostima pod kojima se desio izuzetak, moraćemo sami da ih obezbedimo, tj. da definišemo sopstvene izuzetke
Definisanje naših izuzetaka • Postoje 2 osnovna razloga za definisanje naših klasa izuzetaka: želimo da dodamo informacije kada se desi standardni izuzetak, i možemo to učiniti reizbacivanjem objekta naše klase izuzetaka možemo imati greške koje nastaju u našem kodu koje opravdavaju kreiranje specijalne klase izuzetaka • Definisanje naše klase izuzetaka – naše klase izuzetaka moraju uvek imati Throwable za superklasu. Najbolje je izvoditi ih iz klase Exception, mada se mogu izvesti iz proizvoljne standardne klase izuzetaka. To će dopustiti kompajleru da prati gde u našem programu je izbačen izuzetak i da proverava da li je izuzetak uhvaćen ili deklarisan kao izbačen u metodu. Ako kao superklasu koristimo Runtime. Exception ili neku od njenih potklasa, provera kompajlera biće ukinuta
Definisanje naše klase izuzetaka primer: public class Grozan. Problem. Exception extends Exception { // Konstruktori public Grozan. Problem. Exception(){} public Grozan. Problem. Exception(String s){ super(s); // poziv konstruktora bazne klase } }
Definisanje naše klase izuzetaka • Ovo je minimum koji treba da obezbedimo u definiciji naše klase izuzetaka ( podrazumevani konstruktor koji prihvata String-argument ) • Poruka smeštena u superklasi Exception ( zapravo Throwable) će automatski biti inicijalizovana imenom naše klase, bez obzira na to koji konstruktor naše klase se koristi. String prosleđen drugom konstruktoru biće dopisan na ime klase da bi se formirala poruka koja se čuva u objektu izuzetku • Naravno, mogu se dodati i drugi konstruktori. Generalno, želećemo to, posebno ako reizbacujemo naš izuzetak nakon izbacivanja standardnog izuzetka. • Dodatno, tipično ćemo želeti da dodamo instancne promenljive koje će čuvati dodatne informacije o problemu, plus metode koji će omogućiti kodu u catch bloku da pristupa tim podacima. • Pošto je naša klasa obavezno izvedena iz klase Throwable, informacije steka izvršavanja će biti automatski dostupne našim izuzecima
Izbacivanje našeg izuzetka • throw-naredbom, kao što je ranije rečeno, npr. Grozan. Problem. Exception e = new Grozan. Problem. Exception(); throw e; • Metod će prekinuti izvršavanje u ovoj tački – osim ako je gornji isečak koda u try ili catch bloku, sa pridruženom finally klauzom čiji će se sadržaj izvršiti pre kraja metoda. Izuzetak će biti izbačen u pozivajući program u tački gde je pozvan metod. Poruka u objektu izuzetka će se sastojati samo od kvalifikovanog imena klase izuzetaka. • Ako želimo da dodamo specifičnu poruku izuzetku: Grozan. Problem. Exception e = new Grozan. Problem. Exception("Uh, nevolja!"); • Ovde smo koristili drugi konstruktor
Izbacivanje našeg izuzetka • get. Message() metod nasleđen od Throwable će vratiti String objekat koji sadrži sledeći string: "Grozan. Problem. Exception: Uh, nevolja!" • može i ovako, u jednoj naredbi: throw new Grozan. Problem. Exception("Strasne poteskoce!"); Strategija rukovanjem izuzecima • Potrebno je promisliti šta želimo da postignemo rukovanjem izuzecima u našem programu. Ne postoje čvrsta pravila. U nekim situacijama možemo popraviti problem i omogućiti da program nastavi da se izvršava kao da se ništa nije desilo. U drugim situacijama, ispis steka izvršavanja i brzi završetak programa predstavljaju najbolji pristup – brzi završetak programa se postiže pozivom metoda exit() klase System.
Generički klasni tipovi • Šta su generički tipovi? • Generički tip, koji se takođe naziva i parametrizovani tip, je definicija klase ili interfejsa koja ima jedan ili više parametara. • Stvarni klasni ili interfejsni tip se dobija od generičkog prosleđivanjem argumenta tipa za svaki od parametara koji se pojavljuju u generičkom tipu
Definisanje generičkog klasnog tipa • Na primeru povezane liste • Definicija generičkog klasnog tipa izgleda vrlo slično definiciji obične klase, ali uz zadavanje parametara nakon imena klase. Npr. public class Povezana. Lista<T>{ // Definicija genericke klase … } • Parametar koji se pojavljuje između <> naziva se tipski parametar. Ime, T, identifikuje tipski parametar i to ime parametra se koristi u definicijama metoda i atributa generičkog tipa na mestima gde postoji zavisnost od vrednosti argumenta za taj parametar • Pojavljivanja imena parametra u definiciji generičkog tipa nazivaju se tipske promenljive.
Definisanje generičkog klasnog tipa • Generalno je najbolje da imena parametara budu što je moguće kraća – a idealno jednoslovna. U tekstu će na ime generičkog tipa biti nadovezivano i <> radi lakšeg razlikovanja generičkog tipa od obične klase ili tipa interfejsa. • Moguće je definisati generički klasni tip, ali takođe i generički interfejsni tip. Priličan broj generičkih interfejsnih tipova moguće je naći u standardnim paketima. Kasnije ćemo raditi sa Iterable<> i Comparable<> generičkim interfejsnim tipovima iz paketa java. lang • Da bismo kreirali klasu od generičkog tipa Povezana. Lista<> samo obezbedimo odgovarajući argument za parametar između <> zagrada. Sva pojavljivanja tipske promenljive T u definiciji biće zamenjena datim tipom argumenta. To će rezultirati klasnim tipom koji možemo koristiti da kreiramo objekat koji implementira povezanu listu za smeštanje objekata zadatog tipa • Povezana. Lista <String>, Povezana. Lista <Point>
Argumenti generičkog klasnog tipa • Tako, generički tip u suštini definiše familiju tipova koja se dobija prosleđivanjem različitih argumenata za parametre generičkog tipa. • Kao argument za tipski parametar u generičkom tipu može se staviti samo klasni ili interfejsni tip • Drugim rečima, kao argument se ne može koristiti primitivni tip poput int i double, iako se, naravno, može koristiti tip Integer ili tip Double. • Kada kreiramo određeni tip od definicije generičkog tipa prosleđivanjem vrednosti argumenta za T, argument će zameniti svaku pojavu T-a u specifikaciji generičkog tipa. To se odnosi i na atribute i na definicije metoda u generičkom tipu. • Definicija generičkog tipa smešta se u izvorni fajl sa ekstenzijom. java, upravo kao i obična klasa.
Implementiranje generičkog tipa • Povezana. Lista. java • Klasa Clan. Liste je unutrašnja ( ugnježdena nestatička ) • Definicija generičkog tipa može sadržati obične metode koji ne uključuju nikakve parametre u svojim definicijama kao i metode sa parametrima • Sada imamo generički tip Povezana. Lista<T> koji možemo koristiti da kreiramo novu klasu Povezana. Lista za smeštanje objekata proizvoljnog tipa. Pogledajmo kako da ga koristimo
Instanciranje generičkog tipa • Da bismo definisali novi tip, koristimo ime generičkog tipa praćeno imenom klasnog ili interfejsnog tipa unutar <> zagrada. Npr. Povezana. Lista<String> stringovi; // prom. tipa Povezana. Lista<String> • Ovo samo definiše promenljivu imena stringovi • Tip ove promenljive je Povezana. Lista<String> • Kao rezultat ove naredbe, kompajler će koristiti tipski argument String koji smo obezbedili da zameni svaku instancu tipske promenljive T u definiciji generičkog tipa kako bi došao do definicije za klasni tip Povezana. Lista<String> • Naravno, kada definišemo promenljivu, možemo definisati objekat: Povezana. Lista<String> stringovi = new Povezana. Lista<String>(); • Ovim se poziva podrazumevani konstruktor klasnog tipa Povezana. Lista<String> da definiše objekat koji implementira povezanu listu String objekata
Instanciranje generičkog tipa • Argument koji prosleđujemo generičkom tipu takođe može biti i tipa koji definišemo korišćenjem generičkog tipa. Npr. Povezana. Lista<String>> tekstovi = new Povezana. Lista<String>>(); • Ovde smo definisali povezanu listu povezanih lista • Da bismo primenili novi generički tip Povezana. Lista<> pišemo Poly. Line koristeći tip generisan iz generičkog tipa Povezana. Lista<> • Test. Generic. Povezana. Lista
Komentarisanje programa • Klasa Polyline kreira tip Povezana. Lista<Point> od generičkog tipa Povezana. Lista<T> koji će implementirati povezanu listu Point objekata: private Povezana. Lista<Point> polyline; • Metodi u parametrizovanom tipu se koriste na isti način kao oni u originalnoj definiciji klase Povezana. Lista koja je implementirana bez upotrebe generičkih klasnih tipova.
Upotreba Wrapper klasa primitivnih tipova kao argumenata • U slučaju da želimo da smeštamo vrednosti primitivnog tipa u kolekciju poput povezane liste, koristimo generički tip sa nekom od Wrapper klasa kao tipskim argumentom. To su klase Integer, Short, Double itd. definisane u paketu java. lang • Npr. za upotrebu generičkog tipa Povezana. Lista<T> za smeštanje vrednosti tipa double: Povezana. Lista<Double> temperature = new Povezana. Lista<Double>(); • Ovde smo kreirali povezanu listu za smeštanje objekata tipa Double, a autoboxing omogućuje da se objekat Povezana. Lista<Double> koristi direktno sa vrednostima tipa double. Npr. da bismo efektivno dodali vrednost tipa double povezanoj listi koju smo upravo kreirali: temperature. dodaj. Clan(10. 5); • Pošto je tipski parametar za ovaj metod za objekat tipa Povezana. Lista<Double> tipa Double, kompajler će automatski ubaciti boxing konverziju za konvertovanje double vrednosti 10. 5 u objekat tipa Double koji je enkapsulira.
Višestruki parametri tipa • Generički tip Povezana. Lista<T> ima jedan parametar tipa , T. • U opštem slučaju može se definisati generički tip sa proizvoljnim brojem parametara. • Npr. generički tip koji definiše skup klasa koje enkapsuliraju par objekata proizvoljnih tipova. To se tipično događa kada se 1 objekat koristi kao ključ za pristup drugom u kolekciji. Npr. public class Par<Kljuc. Tip, Vrednost. Tip>{ // Konstruktor public Par(Kljuc. Tip kljuc, Vrednost. Tip vrednost){ this. kljuc=kljuc; this. vrednost=vrednost; } // get*() i set*() metodi private Kljuc. Tip kljuc; private Vrednost. Tip vrednost; }
Višestruki parametri tipa • Praktična definicija će biti komplikovanija od ove, npr. biće potrebno sredstvo za poređenje ključeva, ali ova definicija je dovoljna za demonstriranje korišćenja 2 parametra u definiciji generičkog tipa. • Primer korišćenja ovog generičkog tipa: Par<String, String> unos = new Par<String, String>( "Petar Petrovic", "212 222 3333"); • kreira se objekat tipa Par<String, String> i referenca na njega se smešta u promenljivu unos.
Opseg tipskog parametra • Opseg tipskog parametra je cela definicija generičke klase, ali isključujući statičke članove i inicijalizatore u klasi. • To povlači da se unutar definicije generičkog tipa ne može naći statički atribut tipa tipske promenljive. • Slično, statički metodi ne mogu imati parametre ili povratne tipove koji su tipa tipske promenljive • Tipske promenljive se ne mogu koristiti u telima definicija statičkih metoda • To ne znači da statički metodi ne mogu biti parametrizovani – ovde je reč samo o tipskim promenljivim odgovarajućim za parametre definicije generičkog tipa, a postoje i tzv. generički metodi koji imaju svoju nezavisnu parametrizovanu definiciju koja uključuje sopstveni skup parametara, i takvi parametrizovani metodi mogu biti statički ili nestatički
Generički (parametrizovani) metodi • Moguće je definisati metod sa sopstvenim nezavisnim skupom od 1 ili više tipskih parametara, čime se dobija parametrizovani metod ili generički metod • Možemo imati parametrizovane metode i u običnoj klasi • Metodi unutar definicije generičkog tipa takođe mogu imati nezavisne parametre. Npr. public static <T> void list. All(Povezana. Lista<T> list){ for(T obj: list) System. out. println(obj); }
Generički (parametrizovani) metodi • <T> nakon ključnih reči public i static je lista tipskih parametara za generički metod. Ovde imamo samo jedan tipski parametar, T, ali može ih biti i više. • Lista tipskih parametara za generički metod se uvek ograđuje <> zagradama i treba da prati eventualne modifajere poput public i static i treba da prethodi povratnom tipu. • Generički konstruktori: konstruktor je specijalizovana vrsta metoda i može se definisati i konstruktor sa sopstvenim nezavisnim parametrima. Parametrizovani konstruktori se mogu definisati i za generičke i za obične klasne tipove
Parametrizovani tipovi i nasleđivanje • Klasu je moguće definisati kao potklasu klasnog tipa koji je instanca generičkog tipa • Metodi i atributi će biti nasleđeni od bazne klase na uobičajeni način • Mora se voditi računa da se u izvedenoj klasi ne definišu metodi koji imaju isti potpis kao neki nasleđeni metod. Nizovi i parametrizovani tipovi • Nizovi elemenata tipa dobijenog od generičkog tipa nisu dopušteni. Sledeća naredba će rezultovati porukom kompajlera o grešci: Povezana. Lista<String>[] liste = new Povezana. Lista<String>[10]; • private T[] data; // ovo je ok • data = new T[10]; // Nije dopusteno!!!
Generički tipovi i generički interfejsi • Generički tip može implementirati 1 ili više interfejsnih tipova, uključujući generičke interfejsne tipove. • Sintaksa koja se koristi za ovo je ista kao za obične klase i interfejsne tipove, jedina razlika je što će ime svakog generičkog tipa biti praćeno njegovom listom tipskih parametara između <> zagrada. Npr. public class My. Class<T> implements My. Interface<T>{ // Detalji definicije generickog tipa … }
Kolekcije i collection-based for petlja • Da bi objekat kontejnerskog klasnog tipa bio upotrebljiv sa collection -based for petljom, klasa mora da ispuni jedan zahtev – mora implementirati generički interfejs Iterable<> koji je definisan u paketu java. lang. • Interfejs Iterable<> je generički tip koji deklariše 1 jedini metod iterator(), koji vraća referencu tipa Iterator<> koji je još jedan generički interfejsni tip
Java Collections Framework • Java Collections Framework se sastoji od generičkih tipova koji predstavljaju skupove kolekcijskih klasa. • Ovi generički tipovi definisani su u paketu java. util i obezbeđuju nam mnoštvo načina za struktuiranje i rukovanje kolekcijama objekata u našim programima. • Posebno, tipovi kolekcija nam omogućuju da se nosimo sa situacijama kada ne znamo unapred koliko ćemo objekata imati ili kada nam je potrebno više fleksibilnosti u načinu na koji pristupamo objektu kolekcije nego što imamo indeksiranjem niza
Java Collections Framework • Kolekcijska klasa je prosto klasa koja organizuje skup objekata datog tipa na određeni način, poput povezane liste ili steka • Korišćenje generičkog tipa za naše kolekcije objekata znači da imamo statičku proveru od strane kompajlera tipova koje želimo da obrađujemo. Time se obezbeđuje sprečavanje nemarnih pokušaja smeštanja objekata pogrešnog tipa u kolekciju ( kolekcija je typesafe) • Collections Framework uključuje profesionalnu implementaciju generičkog tipa koji implementira povezanu listu koja je daleko superiornija od povezane liste koju smo sami implementirali. Međutim, naš trud nije bio uzaludan, pošto sada imamo dobru ideju o tome kako funkcionišu povezane liste i kako se definišu generički tipovi.
Java Collections Framework • • Otkrićemo da je Collections Framework važan faktor u većini naših programa. Kada želimo niz koji se automatski proširuje kako bi se prilagodio proizvoljnom broju objekata koji ubacujemo u njega, možemo koristiti odgovarajući generički tip implementiran u Collections Framework-u. U Collections Framework-u ima toliko toga, ali potrudićemo se da pogledamo kako se primenjuju neki reprezentativni primerci kolekcija koji će nam verovatno najčešće trebati: Iterator<T> interfejsni tip, deklariše metode za iteriranje kroz elemente kolekcije, jedan po jedan Vector <T> tip, nizolika struktura za smeštanje proizvoljnog tipa objekata. Broj objekata koje možemo smestiti automatski se povećava po potrebi Stack <T> tip, podržava smeštanje proizv. tipa objekata na stek Linked. List <T> tip, podržava smeštanje proizv. tipa objekata u dvostruko povezanu listu Hash. Map <K, V> tip, podržava smeštanje objekata tipa V u heš tabelu koju nazivamo katalogom/mapom. Objekat se smešta korišćenjem pridruženog ključa koji je objekat tipa K. Da bismo pristupili objektu, moramo proslediti njegov ključ
Kolekcije objekata, opšta razmatranja • • • Objekat tipa Povezana. Lista <T> predstavlja primer kolekcije objekata tipa T, gde T može biti proizvoljni klasni ili interfejsni tip Kolekcija je izraz koji se koristi da opiše objekat koji predstavlja skup objekata grupisanih zajedno i organizovanih na određeni način u memoriji. Klasa koja definiše kolekciju objekata često se naziva kontejnerskom klasom. Postoje 3 osnovna tipa kolekcija za organizovanje objekata na razne načine: skupovi (sets), nizovi (sequences) i mape/katalozi (maps) Kada pričamo o kolekcijama objekata, zapravo mislimo na kolekcije referenci na objekte. U Javi, kolekcije sadrže samo reference na objekte, sami objekti su eksterni za kolekciju
Skupovi • • najjednostavnija vrsta kolekcije objekti nisu uređeni ni na koji način i jednostavno se dodaju skupu bez ikakve kontrole gde će se smestiti. To je kao kada stavljamo stvari u džep – samo ih stavimo unutra možemo dodavati objekte skupu i iterirati kroz sve objekte skupa takođe, možemo proveriti da li je objekat element skupa ili ne u skupu nema ponavljanja elemenata – svaki objekat u skupu mora biti jedinstven možemo i ukloniti dati element iz skupa, ali samo ako imamo referencu na taj objekat u skupu postoje i varijacije skupova – npr. skup može biti uređen. Takvi skupovi zahtevaju da objekti koji se smeštaju u skup budu tipa klase koja definiše pogodne metode za poređenje objekata
Nizovi • • Povezana lista je primer opštijeg tipa kolekcije, niza. Osnovna karakteristika niza je da se objekti smeštaju linearno, ne nužno u određenom redosledu, ali su organizovani u neki proizvoljni fiksirani redosled gde se zna početak i kraj običan niz je takođe primer niza, ali sa mnogo većim ograničenjima u odnosu na ekvivalentnu kolekciju pošto sadrži fiksiran broj elemenata Kolekcije generalno imaju mogućnost da se šire kako bi se prilagodile potrebnom broju elemenata Pošto je niz linearan, novi objekat se može dodati samo na početak ili na kraj ili nakon datog objekta u nizu. Generalno, objektu niza se može pristupiti na nekoliko načina: možemo izabrati prvi ili poslednji, objekat na datoj poziciji – indeksiranje niza, zatim, možemo tražiti objekat jednak datom objektu proverom svih objekata niza bilo unapred, ili unazad Za uklanjanje objekta iz niza imamo iste opcije kao za pristup objektu: možemo ukloniti prvi ili poslednji, objekat sa date pozicije ili objekat jednak datom objektu Nizovi mogu sadržati nekoliko kopija istog objekta na različitim mestima u nizu
Stek • • • Stek ( stack ) last-in first-out (LIFO) struktura je takođe niz Red (queue) first-in first-out (FIFO) struktura Jednostavno se vidi da se povezana lista ponaša kao stek, pošto korišćenje metoda za dodavanje objekata na kraj liste i uklanjanje objekata sa kraja liste čini da se lista ponaša kao stek Slično, samo dodavanje objekata korišćenjem metoda za dodavanje objekata na kraj povezane liste i samo uzimanje objekata sa početka liste čini da se ona ponaša kao FIFO red U Java Collections Framework tipovi koji definišu nizove podeljeni su u 2 grupe: liste i redove Vektori, povezane liste i stekovi su liste, a queue(FIFO) je red
Mape/Katalozi • • • Mape se prilično razlikuju od kolekcija skupova i nizova, pošto svaki njihov unos uključuje par objekata Mape se ponekad zbog načina na koji funkcionišu nazivaju i rečnicima Svaki objekat smešten u mapi ima pridružen objekat ključ i objekat i njegov ključ se smeštaju zajedno, u paru. Ključ određuje gde će objekat biti smešten u mapi, i kada želimo da mu pristupimo, moramo proslediti odgovarajući ključ ( ključ je ekvivalent reči koju tražimo u rečniku ) Ključ može biti proizvoljna vrsta objekta koju želimo da koristimo za referisanje objekata iz mape Pošto ključ mora jedinstveno da identifikuje objekat, svi ključevi u mapi moraju biti različiti Ključ je mehanizam za pristupanje objektima HEŠIRANJE: Gde će par ključ/objekat biti smešten u mapi određeno je procesom koji se zove heširanje Heširanjem se od ključa dobija celobrojna vrednost koja se zove heškod (hashcode) Metod hash. Code() definisan u klasi Object proizvodi heškod tipa int za objekat.
Iteratori • • Iterator je objekat koji možemo da koristimo jedanput za pristup svim objektima kolekcije, jednom po jednom. Korišćenje iteratora je standardni mehanizam za pristup svim elementima kolekcije Svaki objekat kolekcije koji predstavlja skup ili niz može kreirati objekat tipa Iterator<> koji se ponaša kao iterator. Tipovi koji predstavljaju mape nemaju metode za kreiranje iteratora Iterator<> objekat enkapsulira reference na sve objekte kolekcije u nekom redosledu i njima se može pristupiti, jednoj po jednoj, korišćenjem metoda Iterator<> interfejsa
Iteratori • Interfejs Iterator<> ( paket java. util ) deklariše sledeća 3 metoda: T next() vraća objekat tipa T, počev od prvog i postavlja Iterator<T> objekat tako da vrati sledeći objekat prilikom sledećeg poziva ovog metoda. Ako nema objekta koji treba vratiti, metod izbacuje izuzetak No. Such. Element. Exception boolean has. Next() vraća true ako postoji objekat kome će se pristupiti next() metodom, false inače void remove() uklanja poslednji objekat vraćen metodom next() iz kolekcije. Ako next() nije pozvan ili pozovemo remove() 2 puta nakon poziva next(), izbacuje se izuzetak Illegal. State. Exception Ne podržavaju svi iteratori ovaj metod i ukoliko ga pozovemo u tom slučaju, biće izbačen izuzetak Unsupported. Operation. Exception
Iteratori • • Poziv next() metoda za objekat koji implementira Iterator<> vraća uzastopne objekte iz kolekcije, počev od prvog, pa se jednostavno može pomoću petlje proći kroz celu kolekciju: My. Class item; while( iter. has. Next() ) { item = iter. next(); // radimo nesto sa item … } ovde se pretpostavlja da je iter tipa Iterator<My. Class> i čuva referencu na objekat dobijen iz proizvoljne kolekcijske klase Većina objekata kolekcija ima metod iterator() koji vraća iterator za tekući sadržaj kolekcije Metod next() vraća objekat originalnog tipa, pa nema potrebe za kastovanjem Svaki put kada iznova treba proći kroz objekte kolekcije, potreban je novi iterator, pošto je iterator objekat za "jednokratnu upotrebu" Iterator je "jednosmerna ulica" – možemo proći kroz objekte kolekcije, 1 po 1, jednom i to je to. To je uglavnom zadovoljavajuće, ali ne potpuno, pa imamo i druge mogućnosti za pristup celokupnom sadržaju kolekcije Možemo pristupati objektima proizvoljne kolekcije koja implementira interfejs Iterable<> koristeći collection-based for petlju. Ako to nije dovoljno, postoji fleksibilnija vrsta iteratora – list iterator.
List iteratori • • • List. Iterator<> interfejs, definisan u paketu java. util, deklariše metode za kretanje kroz kolekciju unapred i unazad, pa se jednom objektu može pristupiti i više od jedanput List. Iterator<> interfejs nasleđuje Iterator<> interfejsni tip, pa se mogu primenjivati i metodi superinterfejsa koje smo upravo videli. Metodi definisani u List. Iterator<> interfejsu za kretanje kroz listu objekata su: T next() boolean has. Next() kao kod Iterator<> int next. Index() vraća indeks objekta koji će biti vraćen sledećim pozivom next() kao tip int, ili vraća broj elemenata u listi ako je List. Iterator<> objekat na kraju liste T previous() vraća prethodni objekat, koristi se za kretanje unazad kroz listu boolean has. Previous() vraća true ako će sledeći poziv previous() vratiti objekat int previous. Index() vraća indeks objekta koji će biti vraćen sledećim pozivom previous() ili -1 ako je List. Iterator<> objekat na početku liste pozivi next() i previous() mogu se preplitati. Poziv previous() neposredno nakon poziva next() i obrnuto vratiće isti element.
List iteratori • List. Iterator<> metodi za dodavanje, uklanjanje i zamenu objekata kolekcije su: void remove() uklanja poslednji objekat kome je pristupljeno pomoću next() ili previous() (Unsupported. Operation. Exception, Illegal. State. Exception) void add(T obj) dodaje argument neposredno ispred objekta koji bi bio vraćen narednim pozivom next() ili iza objekta koji bi bio vraćen narednim pozivom previous(). Poziv next() nakon add() operacije vratiće dodati objekat. Naredni poziv previous() nije izmenjen ovom operacijom (Unsupported. Operation. Exception, Class. Cast. Exception, Illegal. Operation. Exception) void set(T obj) menja poslednji objekat kome je pristupljeno pozivom next() ili previous() (Illegal. State. Exception, Unsupported. Operation. Exception, Class. Cast. Exception, Illegal. Argument. Exception)
Korišćenje vektora • Generički tip Vector<T> funkcioniše kao niz, ali uz mogućnost da automatski raste kada nam je potreban dodatni kapacitet Kreiranje Vektora Postoje 4 konstruktora: Vector<String> a = new Vector<String>(); podrazumevani, prazan vektor sa kapacitetom za 10 objekata, kapacitet se duplira kada dodamo objekat, a vektor je pun Vector<String> a = new Vector<String>(100); eksplicitno zadajemo kapacitet, opet se kapacitet duplira, nekad neefikasno Vector<String> a = new Vector<String>(100, 10); kapacitet će se povećavati za po 10 elemenata poslednji konstruktor kreira objekat koji sadrži reference na objekte iz druge kolekcije ( prima argument tipa Collection<>)
Vektori, primer • • Primer: vrlo jednostavan, smeštanje nekoliko stringova u vektor import java. util. Vector; public class Test. Jednostavan. Vektor{ public static void main(String[] args){ Vector<String> imena = new Vector<String>(); String[] imena 1 = {"Pera", "Mika", "Laza", "Steva"}; // Dodavanje imena u vektor for(String ime: imena 1) imena. add(ime); // Prikaz sadrzaja vektora for(String ime: imena) System. out. println(ime); } }
Vektori, primer • • • Sve kolekcijske klase koje su nizovi implementiraju Iterable<> interfejs, pa uvek možemo koristiti collection-based for petlju za pristup sadržaju kolekcije. Takođe, može se koristiti i iterator Sledeći kod proizvešće isti rezultat kao poslednja for petlja: java. util. Iterator<String> iter = imena. iterator(); while( iter. has. Next() ) System. out. println( iter. next() ); Za pristup elementima vektora postoji i treći mehanizam. Metod get() Vector<> objekta vraća referencu na objekat čiji je indeks dat kao argument metoda. Argument get() metoda je indeks ( koji kreće od nule ). Da bismo iterirali kroz sve elemente vektora moramo znati koliko elemenata imamo u vektoru, a to možemo dobiti metodom size(). for(int i=0; i<imena. size(); i++) System. out. println(imena. get(i)); Collection-based for petlja je najjednostavniji i najčistiji mehanizam za iteriranje kroz sadržaj vektora. get() metod je koristan za pristup elementu na određenoj poziciji
Kapacitet i veličina vektora • • • Kapacitet vektora je maksimalan broj objekata koje on može sadržati u datom trenutku int imena. Max = imena. capacity(); // vraca trenutni kapacitet imena. ensure. Capacity(150); // postavlja min kapacitet na 150 ako je tekući kapacitet manji od 150, porašće na 150, a ako je 150 ili veći, ova naredba ga neće promeniti Argument je tipa int, ne postoji povratna vrednost Veličina vektora je broj elemenata smeštenih u taj vektor Jasno, veličina vektora ne može biti veća od njegovog kapaciteta imena. set. Size(50); metodom set. Size() možemo povećati i smanjiti veličinu Veličina se postavlja na argument. Ako je veličina bila manja od 50, ostatak se puni null referencama, a ako je bila veća od 50, ostatak se odbacuje. Sami objekti još uvek mogu biti dostupni ako postoje druge reference na njih. imena. trim. To. Size(); // kapacitet se postavlja na velicinu
Smeštanje objekata u vektor • • • imena. add(ime); novi objekat se smešta u vektor i to na kraj svih objekata koji su već u vektoru, veličina vektora se uvećava za 1. Svi "stari" objekti ostaju na istim pozicijama gde su bili i pre ove operacije imena. add(2, ime); smeštanje objekta u vektor na poziciju zadatu prvim argumentom. Ta pozicija mora biti manja ili jednaka veličini vektora ( to znači da se na tom mestu već nalazi neka referenca ili je to pozicija na kraju vektora). Indeksiranje kreće od nule. Novi objekat ime u ovom konkretnom slučaju se smešta ispred objekta koji je ranije imao indeks 2, pa se taj i svi ostali objekti sa većim indeksima pomeraju za 1 udesno i odgovaraće im indeksi za po 1 veći nego pre ove operacije ( Array. Index. Out. Of. Bounds. Exception ) imena. set(2, novo. Ime); promena elementa u vektoru, objekat zadat drugim argumentom smešta se na poziciju u vektoru zadatu prvim argumentom. Metod vraća referencu na objekat prethodno smešten na toj poziciji ( -||- izuzetak )
Smeštanje objekata u vektor • • imena. add. All(imena. Lista); dodavanje svih objekata druge kolekcije u vektor i to na njegov kraj (argument je tipa Collection<>) imena. add. All(i, imena. Lista); -||-, ali ne na kraj, nego počev od pozicije i (Array. Index. Out. Of. Bounds. Exception) Pristup objektima vektora • • • String ime = imena. get(4); // vraca element na zadatoj poziciji povratni tip je određen tipskim argumentom korišćenim za kreiranje Vector<> objekta. Indeks mora biti nenegativan i manji od veličine vektora, inače Array. Index. Out. Of. Bounds. Exception String ime = imena. first. Element(); // vraca prvi element postoji i metod last. Element() koji vraća poslednji element
Pristup elementima vektora preko List iteratora • • • Možemo dobiti List. Iterator referencu iz vektora pozivom metoda list. Iterator() : List. Iterator<String> list. Iter = imena. list. Iterator(); nakon ovoga možemo se kretati unapred i unazad koristeći List. Iterator metode koje smo ranije videli • Moguće je dobiti i List. Iterator<> objekat samo za deo vektora koristeći verziju list. Iterator() metoda sa argumentom koji predstavlja indeks prvog elementa vektora u iteratoru: • List. Iterator<String> list. Iter = imena. list. Iterator(2); (Index. Out. Of. Bounds. Exception) • List<String> lista = imena. sub. List(2, 5); // ekstrahuje elemente 2 do 4 kao podlistu! element sa indeksom određenim drugim argumentom se ne uključuje u listu! (Index. Out. Of. Bounds. Exception) KOMBINACIJE: List. Iterator<String> list. Iter = imena. sub. List(5, 15). list. Iterator(2); prvo se pomoću sub. List() dobije List<String> objekat, pa on vrati list iterator tipa List. Iterator<String> metodom list. Iterator()
Konverzija vektora u niz • • metod to. Array() String[] podaci = imena. to. Array(new String[imena. size()]); argument metoda mora biti niz istog tipa ili supertipa za tip elemenata vektora ( Array. Store. Exception, ako je argument null – Null. Pointer. Exception ) Konverzija niza u vektor • • klasa java. util. Arrays definiše statički generički metod as. List() koji konvertuje niz datog tipa T u kolekciju List<T>. Npr. String[] ljudi = { "Pera", "Mika", "Laza", "Steva" }; List<String> imena. Lista = java. util. Arrays. as. List(ljudi); Vector<String> imena = new Vector<String>(java. util. Arrays. as. List(ljudi));
Uklanjanje objekata iz vektora • remove(3); uklanja element sa zadate pozicije, elementi iza se pomeraju za po 1 mesto ulevo ( Index. Out. Of. Bounds. Exception) Vraća se referenca na uklonjeni objekat. String ime = imena. remove(3); • boolean izbrisan = imena. remove(ime); pretražuje vektor imena od početka i traži prvu referencu na objekat ime i uklanja je. Ako je objekat pronađen i uklonjen iz vektora, vraća true, a inače false remove. All() prihvata argument tipa Collection<> i uklanja elemente te kolekcije ako su prisutni u vektoru. Metod vraća true ako je Vector objekat promenjen ovom operacijom ( bar 1 el. uklonjen ). Može se koristiti u kombinaciji sa metodom sub. List() da ukloni određeni skup elemenata: imena. remove. All(imena. sub. List(5, 15)); ukloniće elemente 5 do 14, uključujući, iz Vector<String> objekta imena, plus eventualne duplikate tih elemenata ako postoje u vektoru •
Uklanjanje objekata iz vektora • • • retain. All() metod očekuje kao argument referencu na tip Collection<> koja sadrži objekte koje treba zadržati. Svi elementi koji nisu u toj kolekciji biće obrisani imena. retain. All(imena. sub. List(5, 15)); vraća true ako je vektor promenjen imena. remove. All. Elements(); uklanja sve elemente vektora, veličina se postavlja na 0 može i imena. clear(); boolean prazan = imena. is. Empty(); vraća true ako je veličina 0, false inače Vector<> objekat može sadržati null reference, ali to ne znači da će size() biti 0 ili metod is. Empty() vratiti true. Da bismo ispraznili Vector<> objekat, moramo zaista ukloniti sve elemente, a ne samo postaviti ih na null.
Pretraživanje vektora • Možemo dobiti poziciju objekta smeštenog u vektoru prosleđujući ga kao argument metodu index. Of(): int pozicija = imena. index. Of(ime); će pretraživati vektor imena od početka tražeći objekat ime, koristeći metod equals za argument, pa klasa objekta ime mora imati odgovarajuću implementaciju metoda equals() da bi ovo radilo. Promenljiva pozicija će dobiti ili indeks prve reference ili -1 ako objekat nije pronađen • • index. Of(ime, 5) - slično, ali traži počev od indeksa 5 npr. ( računa se broj pojavljivanja datog imena u datom vektoru ) String ime = "Pera"; // ime koje se trazi int count = 0; // broj pojavljivanja int position = -1; // pocetni index while ( ++position < imena. size() ){ if((position=imena. index. Of(ime, position))<0) break; ++count; }
Primena vektora • • Guzva Program za modeliranje kolekcije ljudi, imena osoba će se unositi sa tastature Alternative za ispis: for(int i=0; i<film. Cast. size(); i++) System. out. println(film. Cast. get(i)); for(Osoba osoba: film. Cast) System. out. println(osoba); Statički metod ucitaj. Osobu() je pogodan način rukovanja ulazom
Sortiranje kolekcije • • • paket java. util klasa Collections ( ne interfejs Colleciton<> ! ) ima mnoštvo statičkih metoda koji se mogu primeniti na kolekcije 1 od njih je i metod sort() sortira isključivo liste, tj. kolekcije koje implementiraju interfejs List<> ( a to su Vector<>, Stack<> i Linked. List<> ) Očigledno, mora da postoji način na koji će sort() metod utvrditi redosled objekata liste koju sortira, u našem slučaju Osoba objekata Najpogodniji način je da klasa Osoba implementira interfejs Comparable<> Interfejs Comparable<> deklariše samo 1 metod, compare. To(). On vraća -1, 0 ili +1 u zavisnosti od toga da li je tekući objekat manji, jednak ili veći od argumenta prosleđenog metodu. Ako je Comparable<> interfejs implementiran za tip objekata smeštenih u kolekciji, možemo samo proslediti objekat kolekciju kao argument sort() metodu. Kolekcija se sortira "u mestu", pa ovaj metod nema povratnu vrednost.
Sortiranje kolekcije • • • Comparable<> interfejs se može jednostavno implementirati u klasi Osoba: public class Osoba implements Comparable<Osoba>{ // …. // poredjenje Osoba objekata public int compare. To(Osoba osoba){ int rezultat = prezime. compare. To(osoba. prezime); return rezultat==0 ? ime. compare. To(((Osoba)osoba). ime): rezultat; } // … } koristimo compare. To() metod za String objekte da uporedimo prezimena, pa ako su ona jednaka, rezultat određujemo na osnovu imena
Sortiranje kolekcija • • • Možemo samo proslediti Vector<Osoba> objekat metodu sort() i koristiće se metod compare. To() klase Osoba za poređenje članova liste: import java. util. Collections; . . . main. . . { // sve kao i ranije … Collections. sort(film. Cast); System. out. println("n. U abecednom poretku: n"); for(Osoba osoba: film. Cast) System. out. println(osoba); sort() metod je u stvari generički metod i on radi sa proizvoljnim tipom koji implementira Comparable<> interfejs
Stek • • • LIFO ( last-in first-out) generički tip Stack<> je izveden iz Vector<> Klasa Stack<> dodaje 5 metoda onima nasleđenim iz Vector<> T push(T obj) stavlja objekat obj tipa T na vrh steka, vraća referencu koju smo prosledili kao argument T pop() skida objekat sa vrha steka i vraća ga. Referenca se uklanja sa steka. Ako je stek prazan – Empty. Stack. Exception T peek() vraća element sa vrha steka, ali ga ne skida sa steka (Empty. Stack. Exception) int search(Object obj) vraća poziciju na steku reference objekta obj. referenca na vrhu steka je na poziciji 1, sledeća na pozicji 2, itd. Ako objekat nije pronađen na steku, vraća se -1. boolean empty() vraća true ako je stek prazan, false inače
Stek • • • Jedini konstruktor je konstruktor bez argumenata on poziva podrazumevani konstruktor bazne klase Vector<>, pa je inicijalni kapacitet 10 objekata, ali će automatski rasti na isti način kao za vektor push() metod za Stack<> objekat analogan je add() za Vector<>, što dodaje objekat na kraj vektora. Tako, vrh steka odgovara kraju vektora
Stek, primer • • • Deljenje. Karata Možemo koristiti Stack<> objekat, zajedno sa još jednim korisnim metodom klase Collections kako bismo simulirali deljenje karata iz špila. Potreban je i način predstavljanja boja karata i njihovih vrednosti. Tip enum je zadovoljavajući za oba jer ima fiksiran skup konstantnih vrednosti Karta. java Klasa Karta ima 2 atributa, i oba su tipa enumeracije String reprezentacija konstante enumeracije je ime koje smo pridružili toj konstanti! U opštem slučaju, verovatno će nam trebati da poredimo karte, pa klasa Karta može da implementira interfejs Comparable<> compare. To() metod u klasi Karta: ako su dve karte iste boje, porede se vrednosti. Za poređenje enum vrednosti na jednakost koristimo metod equals() Klasa Enum<> koja je bazna za sve enum tipove implementira Comparable<> interfejs, pa koristimo compare. To() metod za utvrđivanje redosleda enum vrednosti
Stek, primer • • Ruka. java Ruku karata podeljenu iz špila možemo predstaviti objektom tipa Ruka. Taj objekat mora da može da se prilagodi proizvoljnom broju karata, pošto to zavisi od igre za koju je ruka namenjena. Možemo definisati klasu Ruka koristeći Vector<Karta> objekat za smeštanje karata. Podrazumevani konstruktor generisan kompajlerom kreiraće Ruka objekat koji sadrzi Vector<Karta> član, ruka dodajemo metod sort() klasi Ruka koji sortira karte u ruci Klasa Karta implementira interfejs Comparable<> pa možemo koristiti statički metod sort() klase Collections za sortiranje karata u ruci return this; vraća tekući Ruka objekat nakon što je sortiran U opštem slučaju možda želimo da poredimo ruke, ali to potpuno zavisi od konteksta. Najbolji pristup za ovo bi da se iz Ruka izvede ruka specifična za datu igru, npr. Poker. Ruka i implementira metod compare. To() interfejsa Comparable<> u toj klasi na način koji odgovara datoj igri.
Stek, primer • • • Spil. java Špil je Stack<Karta> objekat spil metod values() za tip enum vraća kolekciju koja sadrži sve konstante enumeracije podeli. Ruku() metod kreira Ruka objekat i zatim skida broj. Karata karata sa steka spil i dodaje svaku od njih ruci. Zatim se vraća Ruka objekat Potreban nam je metod za mešanje karata pre deljenja import java. util. Collections; . . . Collections. shuffle(spil); . . . metod shuffle() klase Collections meša sadržaj proizvoljne kolekcije koja implementira List<> interfejs. Klasa Stack<> implementria interfejs List<>, pa možemo koristiti metod shuffle() da dobijemo promešani špil Karta objekata. ako koga zanima, vreme potrebno za izvršavanje metoda shuffle() je proporcionalno broju elemenata – ide se unazad kroz listu i swap-uje tekući element sa slučajno izabranim elementom između prvog i tekućeg
Stek, primer • • Test. Deljenje. java Ruka moja. Ruka = spil. podeli. Ruku(5). sort(); podeli. Ruku() metod vraća Ruka objekat pomoću koga zovemo njegov metod sort() Pošto metod sort() vraća referencu na Ruka objekat nakon sortiranja, moguće je u 1 naredbi izvršiti deljenje ruke, sortiranje i naredbu dodele Stek objekat je posebno podesan za deljenje karata, pošto želimo da uklonimo svaku kartu sa špila pošto je podelimo, a to se automatski radi pop() metodom Kada treba da prođemo kroz sve objekte steka, bez njihovog uklanjanja, koristimo collection-based for petlju, kao sa Vector<Karta> objektom u to. String() metodu klase Ruka. Pošto je klasa Stack<> izvedena iz Vector<>, svi metodi klase Vector<> dostupni su i za stek. Korišćenje steka je veoma jednostavno, a stek je moćna alatka u mnogim različitim kontekstima. Stek je često primenjen u aplikacijama koje uključuju sintaksnu analizu, poput kompajlera i interpretera – uključujući i one za Javu
Povezane liste • • • Linked. List<> generički tip implementira uopštenu povezanu listu 2 konstruktora: – podrazumevani – kreira praznu listu – sa argumentom tipa Collection<> koji kreira Linked. List<> objekat koji će sadržati objekte kolekcije prosleđene kao argument add(), add. All() add. First(), add. Last() get(), get. First(), get. Last() remove(), remove. First(), remove. Last() set() size() Kao i za Vector<> objekat, možemo dobiti Iterator<> pozivom iterator() metoda, a List. Iterator<> objekat pozivom list. Iterator() Iterator<> objekat dopušta samo kretanje unapred kroz elemente, dok List. Ierator<> objekat omogućuje kretanje u oba smera
Povezane liste, primer • • • PRIMER: Možemo izmeniti primer Test. Polyline da koristi Linked. List<> objekat, a ne našu "uradi-sam" verziju. Test. Poly. Line i Point klase ostaju iste kao i pre, potrebno je samo promeniti definiciju klase Polyline Klasa je sada dosta jednostavnija pošto klasa Linked. List<> obezbeđuje svu mehaniku operisanja povezanom listom Glavna izmena se tiče klase Polyline i Point objekti su sada smešteni u povezanoj listi implementiranoj Linked. List<> objektom, polyline. Korišćenje kolekcijske klase čini klasu Polyline vrlo pravolinijskom Primer korišćenja collection-based for petlje sa dvodimenzionim nizom: public Polyline(double[][] coords){ for(double[] xy: coords) add. Point(xy[0], xy[1]); } Dvodimenzioni niz je efektivno jednodimenzioni niz referenci na jednodimenzione nizove od kojih svaki ima po 2 elementa – x i y koordinatu tačke. loop-promenljiva xy je tipa double[] i ima 2 elementa
Korišćenje mapa • Mapa je način smeštanja podataka koji minimizuje potrebu za pretraživanjem kada želimo da pristupimo objektu • Svaki objekat ima pridružen ključ koji se koristi za određivanje gde smestiti referencu na objekat, i objekat i ključ se smeštaju zajedno u mapu • Sa datom vrednošću za ključ, uvek se manje-više direktno dolazi do objekta odgovarajućeg za taj ključ
Proces heširanja • Implementacija mape u Java collections framework obezbeđena klasom Hash. Map<> odvojena je od niza u kome se smeštaju ključ/objekat parovi • Indeks za ovaj niz dobija se od ključa korišćenjem heškoda objekta za računanje offset-a u nizu za smeštanje parova ključ/objekat • Po default-u, tu se koristi hash. Code() metod za objekat ključ. Ovaj metod je nasleđen u svim klasama od Object, pa je to metod koji generiše osnovni heškod osim ako hash. Code() metod nije redefinisan u klasi za ključ. Hash. Map<> klasa ne pretpostavlja da je osnovni heškod adekvatan, pa se on dalje transformiše unutar Hash. Map<> objekta. • Unos u tabeli koja se koristi za smeštanje parova ključ/vrednost zove se bucket. Heškod dobijen od ključa određuje bucket u koji će biti smešten par ključ/vrednost.
Proces heširanja • Iako svaki ključ mora biti jedinstven, ne mora se od svakog ključa dobiti jedinstven heškod. Kada dva ili više različitih ključeva daju istu heš-vrednost, to se zove kolizija. Hash. Map<> objekat radi sa kolizijama tako što smešta sve ključ/vrednost parove sa istom hešvrednošću u povezanu listu. Ako se to dešava prečesto, očigledno dolazi do usporavanja procesa smeštanja podataka i pristupanja podacima. • Pristup objektu kod koga je prilikom smeštanja u mapu došlo do kolizije sastoji se iz 2 faze: ključ će biti heširan da se pronađe lokacija na kojoj bi par ključ/vrednost trebalo da bude. Zatim treba pretražiti povezanu listu, da bi se došlo do ključa koji tražimo ( u listi su svi oni ključevi koji daju istu heš-vrednost kao i traženi ključ ) • Zato postoji jak podstrek da se minimizuju kolizije, a cena smanjenja mogućnosti kolizija u heš-tabeli je mnogo praznog prostora u tabeli.
hash. Code() metod • Klasa Object definiše metod hash. Code(), pa se svaki objekat može koristiti kao ključ. • Metod nije baš univerzalno primenljiv. Pošto obično koristi memorijsku adresu na kojoj je objekat smešten za dobijanje hešvrednosti, različiti objekti uvek proizvode različite heš-vrednosti. • To je povoljno jer će operacije nad heš-mapom biti efikasnije, međutim ono što nije dobro je to da različite instance objekta koje sadrže identične podatke proizvode različite heš-vrednosti, pa ih ne možemo porediti. • To postaje smetnja ako koristimo default hash. Code() metod u objektima koje koristimo kao ključeve. U tom slučaju, objektu smeštenom u heš-mapi nikada se ne može pristupiti korišćenjem druge instance objekta ključa, čak i ako je taj objekat ključ identičan u svim ostalim aspektima. A upravo je to slučaj u većini situacija
hash. Code() metod • Npr. razmotrimo aplikaciju poput jednostavnog adresara. • Želimo da smeštamo unose u mapu bazirano na imenima ljudi na koje se unosi odnose, a želimo da pretražujemo mapu na osnovu imena koja se unose sa tastature. • Međutim, objekat koji predstavlja novouneseno ime biće neizbežno različit od onoga koji je korišćen kao ključ za unos i njegovim korišćenjem nećemo moći da nađemo odgovarajući unos za to ime • Rešenje ovog problema bilo bi da se nekako napravi heširanje atributa objekata. Tada, poređenjem vrednosti atributa novog objekta ime sa onima iz objekata imena korišćenih kao ključeva u heš-mapi moći ćemo da pronađemo odgovarajući unos.
Korišćenje objekata naših klasa kao ključeva • Da bi objekti naših klasa mogli da se koriste kao ključevi u heštabeli, mora se predefinisati (override) metod equals() klase Object. • Metod equals() prima kao argument objekat iste klase i vraća boolean vrednost • Ovaj metod se koristi u metodima klase Hash. Map<> za utvrđivanje da li su 2 ključa jednaka, pa naša verzija ovog metoda treba da vrati true kada dva različita objekta sadrže identične vrednosti atributa • Takođe, možemo predefinisati metod hash. Code(), koji vraća hešvrednost za objekat kao tip int.
Generisanje heškodova • To je ogromna tema. Kako ćemo pisati hash. Code() metod u svojoj klasi, zavisi od nas, ali taj metod mora ispunjavati neke zahteve da bi bio od koristi. • heškod koji vraća metod hash. Code() je vrednost tipa int. • Treba da se trudimo da vratimo heškod koji ima veliku verovatnoću da bude jedinstven za objekat i heškodovi koje generišemo za opseg različitih objekata sa kojima ćemo raditi treba da bude što je moguće šire raspodeljen po opsegu int vrednosti. • Da bismo postigli jedinstvenost, tipično ćemo želeti da kombinujemo vrednosti svih atributa u objektu kako bismo dobili heškod. Prvi korak je dobiti integer za svaki atribut. A zatim treba ukombinovati te integer-e za dobijanje vrednosti koja će biti heškod za objekat • Jedan način da se ovo uradi jeste da se svaki integer koji odgovara nekom atributu pomnoži različitim prostim brojem i saberu tako dobijeni rezultati
Generisanje heškodova • To bi trebalo da da razumljivu distribuciju vrednosti sa dobrom verovatnoćom da budu različite za različite objekte • Nije bitno koje proste brojeve ćemo koristiti sve dok: – nisu tako veliki da rezultat izađe iz opsega tipa int – koristimo različit prost broj za svaki atribut • Kako od atributa dobiti integer ? Generisanje integer-a za atribute tipa String je prosto: samo pozovemo hash. Code() metod za atribut, koji je u klasi String implementiran tako da proizvede dobre heškod vrednosti koje će biti jednake za identične stringove. int atribute možemo koristiti takve kakvi su, dok atributi realnih tipova zahtevaju nešto obrade.
Generisanje heškodova - primer • Npr. želimo da koristimo objekte klase Osoba kao ključeve u heš tabeli, a atributi su ime i prezime tipa String i godine tipa int • Mogli bismo hash. Code() metod te klase da implementiramo sa: public int hash. Code() { return 13*ime. hash. Code() + 17*prezime. hash. Code() + 19*godine; } • Ako je atribut objekat tipa neke klase, a ne promenljiva primitivnog tipa, treba implementirati hash. Code() metod za tu klasu i koristiti ga u računanju heškoda za klasu ključa.
Kreiranje hash-map kontejnera • sve map klase implementiraju Map<> interfejs, pa se objekat proizvoljne map klase može referisati promenljivom tipa Map<> • klasa Hash. Map<> je dobra za većinu situacija • konstruktori: za kreiranje Hash. Map<K, V> objekta Hash. Map() kreira mapu kapaciteta dovoljnog za podrazumevani broj objekata (16 , a podrazumevani load faktor je 0. 75) Hash. Map(int capacity) kreira mapu zadatog kapaciteta, load faktor je 0. 75 Hash. Map(int capacity, float load. Factor) kreira mapu zadatog kapaciteta i load faktora kreira mapu sa istim kapacitetom i load faktorom kao što ima mapa prosleđena kao argument
Kreiranje hash-map kontejnera • Primer: kreiranje mape podrazumevanim konstruktorom: Hash. Map<String, Osoba> mapa = new Hash. Map<String, Osoba>() ; ovom naredbom se kreira Hash. Map<> objekat za smeštanje Osoba objekata zajedno sa pridruženim ključevima tipa String • capacity za mapy je broj ključ/objekat parova koje ona može da smesti. Kapacitet se automatski povećava po potrebi, ali to je vremenski zahtevna operacija. capacity vrednost za mapu kombinuje se sa heškodom ključa koji zadamo da bi se izračunao indeks koji određuje gde će objekat i njegov ključ biti smešteni. Da bi ovo izračunavanje dalo dobru distribuciju vrednosti indeksa, kada sami zadajemo kapacitet heš-tabele, trebalo bi da koristimo proste brojeve, npr. Hash. Map<String, Osoba> mapa = new Hash. Map<String, Osoba>(151);
Kreiranje hash-map kontejnera • Broj smeštenih objekata nikada ne može dostići kapacitet. Uvek je potrebno odvojiti kapacitet za efikasnost operacija. Sa nedovoljno tog dodatog kapaciteta, kolizije postaju verovatnije • load-faktor se koristi za odlučivanje kada povećati veličinu heštabele. Kada veličina tabele dostigne vrednost koja je jednaka proizvodu load-faktora i kapaciteta, kapacitet će automatski biti uvećan na dvostruki stari kapacitet +1 ( +1 obezbeđuje da je novi kapacitet bar neparan, ako ne i prost). • 0. 75 je dobar kompromis, a ako hoćemo da ga smanjimo, možemo koristiti 3. konstruktor: Hash. Map<String, Osoba> mapa = new Hash. Map<String, Osoba> ( 151, 0. 6 f ) ;
Smeštanje, pristup i uklanjanje objekata • • sve operacije sa heš-mapom su jednostavne. odgovarajući metodi su: V put(K key, V value) smešta objekat value u mapu korišćenjem ključa zadatog kao prvi argument, value će ukloniti eventualni postojeći objekat i biće vraćena referenca na taj stari objekat. Ako prethodno nije smešten objekat ili je smešten null, vratiće se null. void put. All( ___ map) prenosi sve parove ključ/objekat iz map u tekuću mapu, zamenjujući sve postojeće objekte sa istim ključevima V remove(Object key) uklanja unos pridružen key ako postoji i vraća referencu na objekat. Vraća se null ako ne postoji takav unos ili je null smešten koristeći key. V get(Object key) vraća objekat smešten pomoću key. Ako pomoću key nisu smeštani objekti u mapu ili je smešten null objekat, vraća null. Objekat ostaje u tabeli
Smeštanje, pristup i uklanjanje objekata • metod contains. Key() prima key objekat kao argument i vraća true ako je taj ključ smešten u mapu. Pomoću ovog metoda može se utvrditi da li je null smešten u mapu pomoću datog ključa ili taj ključ uopšte nije korišćen za smeštanje objekata u mapu • Neophodno je osigurati da je vrednost koju vrati put() jednaka null. Inače, možemo nesvesno ukloniti objekat koji je smešten u tabelu korišćenjem istog ključa. • Sledeći fragment koda ilustruje kako bi se to moglo uraditi: Hash. Map<String, Integer> mapa = new Hash. Map<String, Integer>(); String kljuc = "Perica"; int vrednost = 12345; Integer stara. Vrednost = null; for(int i=0; i<4; i++) if( (stara. Vrednost = mapa. put(kljuc, vrednost++)) != null ) System. out. println("odbacili smo vrednost " + stara. Vrednost);
Smeštanje, pristup i uklanjanje objekata • Ovde smo mogli izbaciti svoj izuzetak umesto ispisa poruke • Drugi parametar put() metoda za objekat mapa biće tipa Integer pa kompajler obezbeđuje autoboxing konverziju int vrednosti prosleđene kao argumenta • 12345 12346 12347 • Kada se smešta prva vrednost, ništa nije smešteno od ranije u mapi za taj ključ, pa nema poruke. Za sve uzastopne pokušaje smeštanja objekata koji slede, prethodni objekat se uklanja i vraća se referenca na njega • get() operacija vraća referencu na objekat pridružen ključu, ali ga ne uklanja iz tabele
Smeštanje, pristup i uklanjanje objekata • Da bismo pristupili objektu i obrisali unos koji ga sadrži iz tabele, moramo koristiti metod remove(). int vrednost = mapa. remove(kljuc); ovo uklanja objekat koji odgovara kljuc-u i vraća referencu na njega Ako se ova naredba nadoveže na prethodni fragment koda, biće vraćena referenca na Integer objekat koji enkapsulira vrednost 12348. Pošto to smeštamo u promenljivu tipa int, kompajler će ubaciti unboxing konverziju.
Procesiranje svih elemenata mape • Interfejs Map<> obezbeđuje 3 načina za dobijanje kolekcije sadržaja mape. • Moguće je dobiti sve ključeve Map<K, V> objekta kao objekat tipa Set<K>. • Takođe, moguće je dobiti Collection<V> objekat referenci na sve objekte mape • ključ/objekat parovi smešteni su u mapi kao objekti tipa koji implementira Map. Entry<K, V> interfejs. To je generički interfejsni tip definisan unutar Map<K, V> interfejsa. Možemo dobiti sve parove ključ/objekat iz mape kao objekat tipa Set<Map. Entry<K, V>> • Set<> ili Collection<> objekat koji dobijemo je u suštini pogled na sadržaj mape, tako da se promene Hash. Map<> objekta odražavaju na pridruženi Set<> ili Collection<> , i obrnuto.
Procesiranje svih elemenata mape • Metodi: key. Set() entry. Set() vraća Set<K> objekat referenci na ključeve mape vraća Set< Map. Entry<K, V> > objekat referenci na ključ/objekat parove; svaki par je objekat tipa Map. Entry<K, V> values() vraća Collection<V> objekat referenci na objekte smeštene u mapi • Set<K> objekat koji dobijemo key. Set() metodom možemo koristiti ili direktno za pristup ključevima ili indirektno za dobijanje objekata smeštenih u mapi
Procesiranje svih elemenata mape • Za Hash. Map<String, Integer> objekat mapa, možemo dobiti skup svih ključeva mape sa: Set<String> kljucevi = mapa. key. Set(); • Onda možemo dobiti iterator za ovaj skup ključeva sa: Iterator<String> kljuc. Iter = kljucevi. iterator(); • možemo koristiti metod iterator() za objekat kljucevi za iteriranje preko svih ključeva mape • Moguće je i ukombinovati ove 2 operacije za direktno dobijanje iteratora. Npr. Iterator<String> kljuc. Iter = mapa. key. Set(). iterator(); while(kljuc. Iter. has. Next()) System. out. println(kljuc. Iter. next());
Procesiranje svih elemenata mape • Možemo koristiti ključeve za izdvajanje objekata, ali Collection<> objekat koji vrati metod values() obezbeđuje direktniji način za to. • Primer listanja objekata smeštenih u mapa, pod pretpostavkom da je ona tipa Hash. Map<String, Integer> : Collection<Integer> kolekcija = mapa. values(); for(Integer i: kolekcija) System. out. println(i); • Interfejs Set<> ima Iterable<> za superinterfejs, pa možemo koristiti collection-based for petlju direktno sa objektom koji vrati metod key. Set(): Set<String> kljucevi = mapa. key. Set(); for(String kljuc: kljucevi) System. out. println(kljuc); • mnogo zgodnije i čitljivije nego sa iteratorom, zar ne ? uopšte, collection-based for petlja daje lakše razumljiv kôd nego iterator
Procesiranje svih elemenata mape • Na sličan način kao za skup ključeva, možemo koristiti for petlju za pristup Map. Entry<> objektima Set<Map. Entry<K, V>> objekta koji vrati metod entry. Set(). • Za operisanje Map. Entry<K, V> objektima na raspolaganju su nam sledeći metodi: K get. Key() vraća ključ za Map. Entry<K, V> objekat V get. Value() vraća objekat za Map. Entry<K, V> objekat V set. Value( V new ) postavlja objekat tekućeg Map. Entry<K, V> objekta na argument i vraća originalni objekat. Ovo menja originalnu mapu. (Unsupported. Operation. Exception – ako mapa ne podržava put(), Class. Cast. Exception, Illegal. Argument. Exception)
Procesiranje svih elemenata mape • Objektu Map. Entry<> takođe je potreban equals() metod za poređenja sa drugim Map. Entry<> objektom prosleđenim kao argumentom, i hash. Code() metod za računanje heškoda za Map. Entry<> objekat. • Sa skupom Map. Entry<> objekata možemo pristupati ključevima i odgovarajućim objektima i menjati objekat-deo svakog ključ/objekat para, ako je to potrebno
Primer, heš-mape, Imenik • Primer: vrlo jednostavan imenik koji koristi mapu. Nećemo previše brinuti o oporavku od greške da ne bismo opterećivali kod • Imenik • klase: Osoba, Broj. Telefona, Unos – klasa koja predstavlja unos u imeniku – kombinacija imena i broja. Može se dodati i adresa, ali to za demonstriranje principa nije neophodno. Takođe, definišemo i klasu Imenik. • Osoba. java • Neophodno je poboljšati klasu Osoba koju smo imali u primeru Guzva, tako da njeni objekti postanu upotrebljivi kao ključevi u mapi koju ćemo koristiti – za smeštanje unosa imenika • Dodaćemo metod equals() i predefinisaćemo default hash. Code() metod.
Imenik • da implementiramo metod equals() samo pozovemo compare. To() metod koji smo implementirali za Comparable<> interfejs. • Možemo dodati još nešto korisno, a to je statički metod koji će učitavati podatke za Osoba objekat sa tastature • Broj. Telefona. java • ovde se može izvršavati gomila provera validnosti brojeva, ali to za ovaj primer nije od značaja • dodajemo statički metod za učitavanje broja sa tastature • U praksi bi svakako trebalo proveriti korektnost unosa, ali za prikaz kako funkcionišu heš-mape, to nije potrebno • Unos. java • unos u imeniku kombinuje ime i broj
Imenik • Imenik. java • za smeštanje Unos objekata koristimo Hash. Map<Osoba, Unos> atribut, imenik. • Koristićemo Osoba objekat odgovarajući za unos kao ključ, pa metod dodaj. Unos() ima samo da pristupi Osoba objektu Unos objekta koji mu je prosleđen i da ga koristi kao prvi argument metoda put() za imenik. • Test. Imenik. java • // • Da bismo učinili program malo interesantnijim, dodajemo metod za listanje svih unosa iz imenika u abecednom poretku po imenima. • 1 način da se to uradi bio bi da se kreira povezana lista unosa i iskoristi sort() metod klase Collections da se oni sortiraju • metod sort() očekuje argument tipa List<>, gde tip elemenata liste implementira interfejs Comparable<>. Tako, da bismo mogli da sortiramo unose u imeniku, klasa Unos mora da implementira interfejs Comparable<>.
Imenik • Sada možemo implementirati metod izlistaj. Unose() u klasi Imenik za listanje unosa u abecednom poretku. • to je jednostavno: poziv metoda values() za objekat imenik vraća objekte iz mape, koji su Unos objekti, kao Collection<>. • To prosleđujemo konstruktoru klase Linked. List<Unos> da bismo dobili objekat tog tipa. • Klasa Linked. List<> implementira interfejs List<>, pa se objekat unosi može proslediti kao argument metodu sort() na sortiranje. • Ažurirani main() metod: dodajemo izbor za listanje svih unosa
- Slides: 86