КОЛЕКЦИЈЕ - university of belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/core...

55
1 Поглавље 13 у књизи: „Core Java - Volume 1 - Fundamentals, Eighth Edition, C. Horstmann & G. CornellКОЛЕКЦИЈЕ Структура података за коју се одлучимо може имати велики утицај на то колико ће наша имплементација бити „природна“, као и на њене перформансе. Да ли је потребно брзо претражити хиљаде или чак милионе сортираних елемената? Да ли је потребно брзо уметати или уклањати елементе из средине сортиране секвенце? Да ли је потребно успоставити везе између кључева и вредности? У овом поглављу теорија је прескочена и акценат је на томе како користити колекцијске класе из стандардне библиотеке. Колекцијски интерфејси У иницијалној верзији Јаве постојао је само мали скуп класа за најкорисније структуре података: Vector, Stack, Hashtable, BitSet и интерфејс Enumeration који обезбеђује апстрактни механизам за посећивање елемената произвољног контејнера. Раздвајање колекцијских интерфејса и имплементације Раздвајање интерфејса и имплементације је уобичајено за савремене библиотеке структура података. Погледајмо ово раздвајање на познатој структури података, реду. Интерфејс за ред одређује да је могуће додавати елементе на крај реда, уклањати их са његовог почетка и установити колико елемената се налази у реду. Ред користимо када је потребно да објекте прикупљамо и дохватамо у маниру „first in first out“ (први пристигао - први обрађен). Минимални интерфејс за ред могао би да изгледа овако: interface Queue<E>{ // uproscena forma interfejsa iz standardne biblioteke void add(E element); E remove(); int size(); } Интерфејс нам не говори ништа о томе како је ред имплементиран. Постоје две уобичајене имплементације реда, једна користи „циркуларни низ“, а друга повезану листу:

Upload: others

Post on 17-Aug-2020

12 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

1

Поглавље 13 у књизи: „Core Java - Volume 1 - Fundamentals, Eighth Edition, C. Horstmann & G. Cornell“

КОЛЕКЦИЈЕ Структура података за коју се одлучимо може имати велики утицај на то колико ће наша имплементација бити „природна“, као и на њене перформансе. Да ли је потребно брзо претражити хиљаде или чак милионе сортираних елемената? Да ли је потребно брзо уметати или уклањати елементе из средине сортиране секвенце? Да ли је потребно успоставити везе између кључева и вредности? У овом поглављу теорија је прескочена и акценат је на томе како користити колекцијске класе из стандардне библиотеке. Колекцијски интерфејси У иницијалној верзији Јаве постојао је само мали скуп класа за најкорисније структуре података: Vector, Stack, Hashtable, BitSet и интерфејс Enumeration који обезбеђује апстрактни механизам за посећивање елемената произвољног контејнера. Раздвајање колекцијских интерфејса и имплементације Раздвајање интерфејса и имплементације је уобичајено за савремене библиотеке структура података. Погледајмо ово раздвајање на познатој структури података, реду. Интерфејс за ред одређује да је могуће додавати елементе на крај реда, уклањати их са његовог почетка и установити колико елемената се налази у реду. Ред користимо када је потребно да објекте прикупљамо и дохватамо у маниру „first in first out“ (први пристигао - први обрађен). Минимални интерфејс за ред могао би да изгледа овако:

interface Queue<E>{ // uproscena forma interfejsa iz standardne biblioteke void add(E element); E remove(); int size(); }

Интерфејс нам не говори ништа о томе како је ред имплементиран. Постоје две уобичајене имплементације реда, једна користи „циркуларни низ“, а друга повезану листу:

Page 2: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

2

Figure 13-2 Queue Implementations Почев од Java SE 5.0 колекцијске класе су генеричке класе са типским параметрима.

Page 3: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

3

Свака имплементација може се изразити класом која имплементира интерфејс Queue: class CircularArrayQueue<E> implements Queue<E> { // nije zaista bibliotecka klasa CircularArrayQueue(int capacity){…} public void add(E element){…} public E remove() {…} public int size() {…} private E[] elements; private int head; private int tail; } class LinkedListQueue<E> implements Queue<E> { // nije zaista bibliotecka klasa LinkedListQueue() {…} public void add(E element) {…} public E remove() {…} public int size() {…} private Link head; private Link tail; } Јавина библиотека заправо нема класе које се зову CircularArrayQueue и LinkedListQueue. Оне су овде искоришћене само као примери за објашњење концептуалних разлика између колекцијских интерфејса и имплементација. Уколико Вам је потребна имплементација реда која користи циркуларни низ, користите класу ArrayDeque, која је уведена у Java SE 6. Класа LinkedList је имплементација реда која користи повезану листу (и имплементира интерфејс Queue). Када у свом програму користите ред, не морате да знате која имплементација се заправо користи након што је колекција конструисана. Према томе, има смисла користити конкретну класу само када се конструише објекат који представља колекцију, а чувати референцу на добијену колекцију у променљивој типа интерфејса:

Queue<Kupac> brzaKasa = new CircularArrayQueue<Kupac>(100); brzaKasa.add(new Kupac("Pera"));

Оваквим приступом, уколико се предомислимо, лако можемо употребити другу имплементацију. Неопходно је извршити промену само на једном месту у програму - у позиву конструктора. Нпр. уколико одлучимо да је LinkedListQueue бољи избор, наш кôд постаје:

Queue<Kupac> brzaKasa = new LinkedListQueue<Kupac>(100); brzaKasa.add(new Kupac("Pera"));

Зашто бисмо бирали између различитих имплементација? Интерфејс не говори ништа о ефикасности имплементације. Циркуларни низ је донекле ефикаснији од повезане листе, па је генерално пожељнији. Међутим, као и обично, постоји цена коју треба платити. Циркуларни низ је ограничена колекција - капацитет је коначан. Уколико немамо горње ограничење за број објеката

Page 4: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

4

које ће наш програм прикупити, можда је ипак боље да користимо имплементацију са повезаном листом. У Јавиној библиотеци постоји и скуп класа чија имена почињу са „Abstract“. Нпр. AbstractQueue. Те класе су намењене имплементаторима библиотеке. У мало вероватном случају да желимо да имплементирамо своју класу која представља ред, вероватно нам је једноставније да изведемо поткласу из класе AbstractQueue, него да имплементирамо све методе интерфејса Queue. Колекцијски и итератор интерфејси у Јавиној библиотеци Фундаментални интерфејс за колекцијске класе Јавине библиотеке је интерфејс Collection. Овај интерфејс има два фундаментална метода:

public interface Collection<E>{ boolean add(E element); Iterator<E> iterator(); … }

Осим ова два, постоји још неколико метода о којима ће више речи бити касније. Метод add() додаје елемент у колекцију. Враћа true ако додавање елемента заиста измени колекцију, а false уколико колекција остане непромењена. Нпр. ако покушамо додавање објекта у скуп, а објекат већ постоји унутра, позив метода add() нема ефекта, јер скуп не прихвата дупликате. Метод iterator() враћа објекат класе која имплементира интерфејс Iterator. Објекат итератор се може користити за посећивање објеката колекције, једног по једног. Итератори Интерфејс Iterator поседује 3 метода:

public interface Iterator<E>{ E next(); boolean hasNext(); void remove(); }

Узастопним позивима метода next() могуће је посетити све елементе колекције, један по један. Међутим, ако смо дошли до краја колекције, метод next() избацује NoSuchElementException. Према томе, пре позива метода next() потребно је позивати метод hasNext(). Овај метод враћа true ако има још објеката које итератор није посетио. Ако желимо да обиђемо све елементе колекције, тражимо итератор, а потом позивамо метод next() све док метод hasNext() враћа true. Нпр.

Collection<String> c = …; Iterator<String> iter = c.iterator(); while(iter.hasNext()){ String element = iter.next(); ради се нешто са елементом }

Page 5: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

5

Почев од Java SE 5.0, постоји елегантно скраћење за овакву петљу. То је тзв. „for each“ петља:

for(String element: c){ ради се нешто са елементом }

Компајлер просто „for each“ петљу преводи у петљу која користи итератор. „For each“ петља ради са објектима произвољне класе која имплементира интерфејс Iterable. Овај интерфејс има само један метод:

public interface Iterable<E>{ Iterator<E> iterator(); }

Интерфејс Collection изведен је из интерфејса Iterable. Према томе, могуће је користити „for each“ петљу са (скоро) сваком колекцијом из стандардне библиотеке (колекције чија се имена завршавају са „Map“ не имплементирају интерфејс Collection). Међутим, ако идемо кроз елементе из HashSet, наилазићемо на њих у потпуно случајном поретку. Можемо бити сигурни да ћемо итерирањем обићи све елементе колекције, али не можемо правити никакве претпоставке о њиховом поретку. То обично не представља проблем. О итераторима се може мислити као о међуелементима. Када позовемо next(), итератор „прескаче“ следећи елемент и враћа референцу на елемент који је управо прошао:

Page 6: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

6

Уклањање елемената Метод remove() интерфејса Iterator уклања елемент враћен последњим позивом метода next(). У многим ситуацијама ово има смисла - треба да видимо елемент пре него што одлучимо да ли га треба уклонити. Међутим, уколико желимо да уклонимо елемент са задате позиције, ипак треба да га прођемо. Нпр. ево како се уклања први елемент колекције стрингова c:

Iterator<String> it = c.iterator(); it.next(); // preskocimo prvi element it.remove(); // sada ga uklonimo

Још је битније да постоји веза између позива метода next() и remove(). Није исправно позвати метод remove() ако му није претходио позив метода next(). Уколико ипак пробамо, долази до избацивања IllegalStateException. Ако желимо да уклонимо два суседна елемента, не можемо просто позвати:

it.remove(); it.remove(); // Greska!

Уместо тога, прво се мора позвати метод next() како би „скочио“ преко елемента који треба уклонити:

it.remove(); it.next(); it.remove(); // OK

Generic Utility Methods Како су интерфејси Collection и Iterator генерички, могуће је писати „utility“ методе који оперишу произвољном врстом колекција. Нпр. следи генерички метод који тестира да ли произвољна колекција садржи дати елемент:

public static <E> boolean contains(Collection<E> c, Object obj){ for(E element : c) if(element.equals(obj)) return true; return false; }

Дизајнери Јавине библиотеке одлучили су да су неки од ових метода толико корисни да би требало да буду доступни у библиотеци. На тај начин, корисници библиотеке не морају измишљати рупу на саксији . Метод contains() је један од тих метода. Заправо, интерфејс Collection дефинише приличан број корисних метода које морају обезбедити све класе које имплементирају овај интерфејс. Између осталих, то су методи:

int size() boolean isEmpty() boolean contains(Object obj) boolean containsAll(Collection<?> c)

Page 7: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

7

boolean equals(Object other) boolean addAll(Collection<? extends E> from) boolean remove(Object obj) boolean removeAll(Collection<?> c) void clear() boolean retainAll(Collection<?> c) Object[] toArray() <T> T[] toArray(T[] arrayToFill)

Имена већине ових метода довољно говоре сама за себе. Наравно, напорно је да свака класа која имплементира интерфејс Collection обезбеди толико метода. Како би се имплементаторима живот учинио лакшим, библиотека поседује класу AbstractCollection која оставља фундаменталне методе size() и iterator() апстрактним, док их имплементације осталих метода користе. Нпр.

public abstract class AbstractCollection<E> implements Collection<E> { … public abstract Iterator<E> iterator(); public boolean contains(Object obj){ for(E element : c) // calls iterator() if(element.equals(obj)) return true; return false; } … }

Конкретна колекцијска класа сада може да се изведе из класе AbstractCollection. Она треба да обезбеди метод iterator(), док се за метод contains() побринула базна класа AbstractCollection. Међутим, уколико поткласа има ефикаснији начин за имплементирање метода contains(), слободна је да га употреби. java.util.Collection<E> Iterator<E> iterator() враћа итератор који се може користити за посећивање елемената колекције. int size() враћа број елемената тренутно смештених у колекцију. boolean isEmpty() враћа true ако колекција не садржи елементе. boolean contains(Object obj) враћа true ако колекција садржи објекат једнак са obj. boolean containsAll(Collection<?> other) враћа true ако колекција садржи све елементе колекције other.

Page 8: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

8

boolean add(Object obj) додаје елемент у колекцију. Враћа true ако је колекција измењена овим позивом. boolean addAll(Collection<? extends E> other) додаје све елементе колекције other у текућу колекцију. Враћа true ако је колекција измењена овим позивом. boolean remove(Object obj) уклања објекат једнак са obj из колекције. Враћа true уколико је одговарајући објекат уклоњен. boolean removeAll(Collection<?> other) уклања из текуће колекције све елементе колекције other. Враћа true ако је колекција измењена овим позивом. void clear() уклања све елементе колекције. boolean retainAll(Collection<?> other) уклања све елементе колекције који нису једнаки ниједном од елемената колекције other. Враћа true ако је колекција промењена овим позивом. Object[] toArray() враћа низ објеката из колекције. <T> T[] toArray(T[] arrayToFill) враћа низ објеката из колекције. Ако је arrayToFill довољне дужине, пуни се елементима колекције. Ако има још простора, попуњава се null вредностима. Иначе, алоцира се нови низ истог типа као arrayToFill и дужине колика је величина колекције и попуњава се елементима колекције. java.util.Iterator<E> boolean hasNext() враћа true ако има још непосећених елемената. E next() враћа следећи елемент који треба посетити. Избацује NoSuchElementException ако се стигло до краја колекције. void remove() уклања последњи посећени објекат. Мора уследити непосредно након посете том елементу. Ако је колекција измењена након последње посете, избацује IllegalStateException.

Page 9: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

9

Конкретне колекције У табели која следи набројане су колекције из Јавине библиотеке и кратак опис сваке од њих. Конкретне колекције из Јавине библиотеке Класа Опис ArrayList индексирана секвенца који динамички расте и смањује се LinkedList уређена секвенца која омогућује ефикасно уметање и уклањање са произвољне позиције ArrayDeque ред са два краја имплементиран као циркуларни низ HashSet неуређена колекција која не допушта дупликате TreeSet сортирани скуп EnumSet скуп вредности типа енумерације LinkedHashSet скуп који памти редослед додавања елемената PriorityQueue колекција која омогућује ефикасно уклањање најмањег елемента HashMap структура података која чува парове кључ/вредност TreeMap мапа у којој су кључеви сортирани EnumMap мапа у којој су кључеви типа енумерације LinkedHashMap мапа која памти редослед додавања елемената WeakHashMap мапа са вредностима које garbage collector може уклонити ако се не користе нигде другде IdentityHashMap мапа чији се кључеви пореде са ==, не са equals() Повезане листе Низ и објекат класе ArrayList имају велики недостатак: уклањање елемента из средине низа је скупо јер сви елементи који следе морају да се помере ка почетку низа (видети наредну слику). Исто важи и за уметање елемената у средину.

Page 10: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

10

Добро позната структура података, повезана листа, решава овај проблем. Док низ чува референце на објекте на узастопним меморијским локацијама, повезана листа чува сваки објекат у посебном „линку“. Сваки линк такође чува референцу на наредни линк у секвенци. У Јави су све повезане листе заправо двоструко повезане, тј. сваки линк такође чува и референцу на свог претходника :

Уклањање елемента из средине повезане листе није скупа операција - потребно је ажурирати само линкове који окружују тај елемент. Следећи фрагмент кôда додаје три елемента, а потом уклања други: List<String> osoblje = new LinkedList<String>(); // LinkedList implementira List osoblje.add("Amy"); osoblje.add("Bob"); osoblje.add("Carl"); Iterator iter = osoblje.iterator(); String prvi = iter.next(); // poseti prvi element String drugi = iter.next(); // poseti drugi element iter.remove(); // ukloni poslednji poseceni element

Page 11: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

11

Постоји битна разлика између повезаних листи и генеричких колекција. Повезана листа је уређена колекција у којој је позиција елемената битна. Метод add() додаје елемент на крај листе. Међутим, често желимо да додамо елемент негде у средину листе. За такав метод add() је задужен итератор пошто итератори описују позиције у колекцијама. Коришћење итератора за додавање елемената има смисла само за колекције које имају природно уређење. Нпр. скуп нема никакво уређење елемената. Према томе, нема метода add() у интерфејсу Iterator, али постоји подинтерфејс ListIterator, који садржи метод add():

interface ListIterator<E> extends Iterator<E>{ void add(E element); … }

За разлику од метода add() интерфејса Collection, овај метод не враћа boolean - претпоставља се да операција add() увек мења листу. Додатно, интерфејс ListIterator поседује два метода која се могу користити за обилажење листе уназад: E previous() boolean hasPrevious() Попут метода next(), метод previous()враћа објекат преко кога је итератор управо прешао. Метод listIterator() класе LinkedList враћа итератор, објекат класе која имплементира интерфејс ListIterator. ListIterator<String> iter = osoblje.listIterator();

Page 12: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

12

Metod add() додаје нови елемент испред позиције итератора. Нпр. следећи фрагмент кôда пролази први елемент и умеће ”Juliet” испред другог:

List<String> osoblje = new LinkedList<String>(); osoblje.add("Amy"); osoblje.add("Bob"); osoblje.add("Carl"); ListIterator<String> iter = osoblje.listIterator(); iter.next(); // preskoci iza prvog elementa iter.add("Juliet");

Ако се метод add()позове више пута, елементи се просто додају тим редом, један за другим, испред текуће позиције итератора. Када се операција add()користи за новокреирани итератор који показује на почетак повезане листе, додати елемент постаје глава листе. Када је итератор иза последњег елемента листе (тј. hasNext() је вратио false), додати елемент постаје нови реп листе. Ако повезана листа има n елемената, постоји n+1 позиција за додавање новог елемента. Ове позиције одговарају (n+1)-ој могућој позицији итератора. Нпр. ако повезана листа садржи три елемента, А, B и C, тада постоје 4 могуће позиције (означене са |) за уметање новог елемента:

|ABC A|BC AB|C ABC|

Page 13: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

13

Непосредно након позива next(), метод remove() уклања елемент лево од итератора. Међутим, ако је управо позван метод previous(), уклања се елемент десно. Није могуће звати remove() два пута узастопно. Метод set() замењује последњи елемент враћен позивом метода next() или previous() новим елементом. Нпр. следећи фрагмент кôда замењује први елемент листе новом вредношћу:

ListIterator<String> iter = lista.listIterator(); String staraVrednost= iter.next(); // vraca prvi element iter.set(novaVrednost); // postavlja prvi element na novaVrednost

Као што се може претпоставити, ако један итератор обилази колекцију док је други модификује, могу настати конфликтне ситуације. Нпр. претпоставимо да један итератор показује испред елемента који је други итератор управо уклонио. Први итератор више није валидан и не би смео даље да се користи. Итератори за повезане листе дизајнирани су тако да детектују такве модификације. Ако итератор открије да је његова колекција измењена другим итератором или позивом неког метода над колекцијом, он избацује ConcurrentModificationException. Нпр.

List<String> lista = …; ListIterator<String> iter1 = lista.listIterator(); ListIterator<String> iter2 = lista.listIterator(); iter1.next(); iter1.remove(); iter2.next(); // izbacuje ConcurrentModificationException

Позив iter2.next() избацује ConcurrentModificationException јер iter2 открива да је листа екстерно модификована. Како би се спречило избацивање изузетка због конкурентне модификације, потребно је поштовати једноставно правило. Могуће је придружити колекцији произвољан број итератора ако су сви они само читачи. Алтернативно, могуће је придружити само један итератор који може и да чита и да пише. Детекција конкурентног модификовања постиже се једноставно. Колекција прати број извршених мутирајућих операција (попут додавања и уклањања елемената). Сваки итератор има свој бројач таквих операција које је он извршио. На почетку сваког метода итератора, итератор просто провери да ли је његов бројач једнак са бројачем колекције. Ако није, избаци ConcurrentModificationException. Постоји, међутим, занимљив изузетак за детекцију конкурентних модификација. Повезана листа прати само структуралне модификације, попут додавања и уклањања линкова. Метод set() се не убраја у структуралне модификације. Могуће је придружити већи број итератора повезаној листи од којих сваки позива set() како би променио садржај постојећих линкова. Оваква могућност неопходна је за велики број алгоритама класе Collections. Као што смо видели, ListIterator се може користити за обилажење листе у произвољном смеру и за додавање и уклањање елемената.

Page 14: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

14

Многи други корисни методи за оперисање повезаним листама декларисани су у интерфејсу Collection. Они су, већим делом, имплементирани у суперкласи AbstractCollection класе LinkedList. Нпр. метод toString() позива toString() за елементе и производи један дуг стринг облика [A, B, C]. То је згодно приликом исправљања грешака. Метод contains() користи се за утврђивање да ли је елемент присутан у повезаној листи. Нпр. позив osoblje.contains("Pera") враћа true ако повезана листа већ садржи стринг једнак стрингу "Pera". Повезана листа не подржава брз случајан приступ. Ако желимо да видимо n-ти елемент, морамо да пођемо од почетка и најпре пређемо преко првих n-1 елемената. Не постоји пречица. Из тог разлога програмери обично не користе повезане листе у ситуацијама у којима је потребно приступати елементима коришћењем целобројног индекса. Без обзира на то, класа LinkedList поседује метод get()који омогућује приступ одређеном елементу: LinkedList<String> lista = … ; String obj = lista.get(n); Наравно, овај метод није ефикасан. Уколико затекнете себе како га користите, вероватно користите структуру података неадекватну за Ваш проблем. Никада не треба да користите овај илузорни метод случајног приступа за пролазак кроз повезану листу. Кôд for(int i=0; i<lista.size(); i++) radi se nesto sa lista.get(i); je запањујуће неефикасан. Сваки пут када трагамо за наредним елементом, потрага почиње од почетка листе. Објекат класе LinkedList не труди се да кешира информацију о позицији. Метод get() има једну незнатну оптимизацију: ако је индекс бар size()/2, потрага за елементом почиње од краја листе. Интерфејс ListIterator такође поседује метод који враћа индекс текуће позиције. Заправо, како итератори концептуално показују између елемената, постоје два таква метода: nextIndex(), који враћа целобројни индекс елемента који ће бити враћен наредним позивом метода next(), и previousIndex(), који враћа индекс елемента који ће бити враћен наредним позивом метода previous(). Наравно, то је за један мање од nextIndex(). Ови методи су ефикасни - итератори прате текућу позицију. Најзад, ако имамо целобројни индекс n, тада lista.listIterator(n) враћа итератор који показује непосредно испред елемента са индексом n. Позив метода next() враћа исти елемент као и lista.get(n). Добијање тог итератора је неефикасно. Ако имамо повезану листу са малим бројем елемената, не треба да будемо превише параноични око цене get() и set() метода. Али, на првом месту, због чега онда користити повезану листу? Једини разлог за коришћење повезане листе је минимизација цене уметања и уклањања из средине листе. Ако имате само неколико елемената, можете користити ArrayList.

Page 15: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

15

Препорука је држати се подаље од свих метода који користе целобројни индекс за означавање позиције у повезаној листи. Ако желите случајан приступ у колекцији, користите низ или ArrayList, а не повезану листу. Пример 1 (LinkedListTest): Просто креира две листе, учешљава их, потом уклања сваки други елемент из друге листе и коначно тестира метод removeAll(). Препорука је пратити ток извршавања програма и посебно обратити пажњу на итераторе. Може бити од помоћи цртање дијаграма позиција итератора, попут:

|ACE |BDFG A|CE B|DFG AB|CE B|DFG …

Приметити да позив System.out.println(a); штампа све елементе повезане листе позивајући метод toString() класе AbstractCollection. java.util.List<E> ListIterator<E> listIterator() враћа list iterator за посећивање елемената листе. ListIterator<E> listIterator(int index) враћа list iterator за посећивање елемената листе чији ће први позив метода next() вратити елемент са задатим индексом. void add(int i, E element) додаје елемент на задату позицију. void addAll(int i, Collection<? extends E> elements) додаје све елементе из колекције на задату позицију. E remove(int i) уклања и враћа елемент са задате позиције. E get(int i) дохвата елемент са задате позиције. E set(int i, E element) замењује елемент на задатој позицији новим и враћа стари елемент. int indexOf(Object element) враћа позицију прве појаве елемента једнаког задатом или -1 ако нема таквог елемента. int lastIndexOf(Object element) враћа позицију последње појаве елемента једнаког задатом или -1 ако нема таквог елемента.

Page 16: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

16

java.util.ListIterator<E> void add(E newElement) додаје елемент испред текуће позиције. void set(E newElement) замењује последњи елемент посећен позивом next()или previous() новим елементом. Избацује IllegalStateException ако је структура листе промењена од последњег позива next() или previous(). boolean hasPrevious() враћа true ако постоји елемент који ће бити посећен итерирањем уназад кроз листу. E previous() враћа претходни објекат. Избацује NoSuchElementException ако је достигнут почетак листе. int nextIndex() враћа индекс елемента који ће бити враћен наредним позивом метода next(). int previousIndex() враћа индекс елемента који ће бити враћен наредним позивом метода previous(). java.util.LinkedList<E> LinkedList() конструише празну повезану листу. LinkedList(Collection<? extends E> elements) конструише повезану листу и додаје јој све елементе из колекције. void addFirst(E element) void addLast(E element) додаје елемент на почетак, односно крај листе. E getFirst() E getLast() враћа елемент са почетка, односно краја листе. E removeFirst() E removeLast() уклања и враћа елемент са почетка, односно краја листе. Array Lists У претходној секцији видели смо интерфејс List и класу LinkedList која га имплементира. Интерфејс List описује уређену колекцију у којој је позиција елемената битна. Постоје два протокола за посећивање елемената: помоћу итератора и случајним приступом помоћу метода get() и set(). Овај други није погодан за повезане листе, али, наравно, get() и set()имају

Page 17: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

17

пуно смисла за низове. У библиотеци колекција је слична класа ArrayList, која такође имплементира интерфејс List. Она енкапсулира низ објеката који се динамички реалоцира. Јава програмери који су ветерани користе класу Vector кад год им је потребан динамички низ. Зашто би требало користити ArrayList уместо Vector? Из једноставног разлога. Сви методи класе Vector су синхронизовани (synchronized). Безбедно је приступити објекту класе Vector из две нити. Али ако приступате вектору из само једне нити - што је најчешћи случај - Ваш кôд траћи прилично времена на синхронизацију. Супротно томе, методи класе ArrayList нису синхронизовани. Препорука је користити ArrayList уместо Vector кад год нам није потребна синхронизација. Hash Sets Повезане листе и низови омогућавају задавање поретка којим ће елементи бити смештени. Међутим, ако тражимо одређени елемент и не сећамо се његове позиције, морамо обилазити редом све елементе док га не пронађемо. То може бити временски захтевно ако колекција садржи много елемената. Ако нам поредак елемената није важан, постоје друге структуре података које омогућују много бржу претрагу. Недостатак је што те структуре не пружају контролу над редоследом којим ће се елементи појављивати. Оне организују елементе редом који је подесан за њихову сопствену сврху. Хеш-табела је добро позната структура података за брзо тражење објеката. Хеш-табела израчунава цео број, тзв. хеш-кôд, за сваки објекат. Хеш-кôд је број који је некако изведен од инстанцних чланица објекта, пожељно тако да објекти са различитим подацима воде различитим кôдовима. Када дефинишете своје класе, Ви сте одговорни за имплементирање сопственог метода hashCode(). Ваша имплементација треба да буде компатибилна са методом equals(): ако је a.equals(b), онда a и b морају имати исти хеш-кôд. Важно је да се хеш-кôд рачуна брзо и да израчунавање зависи само од стања објекта који се хешира, а не и од других објеката у хеш-табели. У Јави, хеш-табеле су имплементиране као низови повезаних листи. Свака листа се зове „bucket“ (слика испод). Да би се одредила позиција објекта у табели, израчунава се његов хеш-кôд и нађе остатак при дељењу укупним бројем bucket-a. Тако добијени број је индекс bucket-a који садржи тај елемент. Нпр. ако објекат има хеш-кôд 76268 и има 128 bucket-a, објекат се смешта у bucket 108 (јер је остатак 76268 % 128 једнак 108). Можда имамо среће и нема других елемената у том bucket-у. Онда се елемент просто убаци у bucket. Наравно, неизбежно је да се понекад деси да је bucket већ пуњен. Тада долази до тзв. колизије. У том случају, нови објекат се пореди са свим објектима из bucket-a како би се видело да ли је већ присутан. Ако хеш-кôдови имају разумну случајну дистрибуцију и број bucket-a је довољно велик, требало би да буде потребно свега неколико поређења.

Page 18: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

18

Ако желите већу контролу над перформансама хеш-табеле, можете задати иницијални број bucket-a. Тај број представља број bucket-a који се користе за прикупљање објеката са идентичним хеш-вредностима. Ако се превише објеката убаци у хеш-табелу, број колизија расте, а перформансе тражења објеката опадају. Ако знате колико ће приближно елемената на крају бити у табели, можете подесити број bucket-a. Типично, он се поставља на нешто између 75% и 150% очекиваног броја елемената. Неки истраживачи верују да је добра идеја да број bucket-а буде прост број како би се спречило груписање кључева. Међутим, не постоји убедљив доказ за то. Стандардна библиотека за број bucket-a користи степене двојке, подразумевано 16. (Свака вредност коју задате за величину табеле аутоматски бива заокружена на следећи степен двојке.) Наравно, не морате увек знати колико ће елемената бити или ваша иницијална процена може бити премала. Ако се хеш-табела препуни, неопходно је да буде „рехеширана“. Да би се табела рехеширала, неопходно је да се креира табела са већим бројем bucket-a, сви елементи убаце у нову табелу и стара табела одбаци. Тзв. „load factor“ одређује када се табела рехешира. Нпр. ако је load factor 0.75 (што је подразумевано) и табела је попуњена више од 75%, она се аутоматски рехешира са два пута већим бројем bucket-a. За већину примена разумно је оставити да load factor буде 0.75. Хеш-табеле се могу користити за имплементирање неколико важних структура података. Најједноставнија међу њима је скуп (енг. set). Скуп је колекција елемената без дупликата. Метод add() за скуп најпре покушава да пронађе објекат који треба додати и додаје га једино ако није већ присутан у скупу. Јавина библиотека колекција поседује класу HashSet која имплементира скуп користећи хеш-табелу. Елементи се додају методом add(). Metod contains() је предефинисан тако да врши брзу претрагу како би открио да ли је елемент присутан у скупу. Он проверава само елементе једног bucket-a, а не све елементе у колекцији.

Page 19: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

19

HashSet итератор обилази све bucket-e редом. Како хеширање разбацује елементе широм табеле, они бивају посећени наизглед случајним редом. HashSet треба користити једино ако нам није важно уређење елемената у колекцији. Пример 2 (SetTest): Чита речи са стандардног улаза (System.in), додаје их у скуп и на крају штампа првих 20 речи из скупа. Нпр. могуће је као улаз задати текст „Алиса у Земљи Чуда“ (http://www.gutenberg.net) покретањем програма из командне линије са: java SetTest < alice30.txt Програм чита све речи са улаза и додаје их у хеш-сет. Затим итерира кроз речи скупа и штампа њихов број. Алиса у Земљи Чуда има 5909 различитих речи, укључујући copyright напомену на почетку. Речи се појављују случајним редом. ОПРЕЗ: Будите пажљиви када мењате елементе скупа. Ако се тиме мења хеш-кôд елемента, елемент више неће бити на исправној позицији у структури података. java.util.HashSet<E> HashSet() конструише празан хеш-сет. HashSet(Collection<? extends E> elements) конструише хеш-сет и додаје у њега елементе из колекције. HashSet(int initialCapacity) конструише празан хеш-сет задатог капацитета (број bucket-a). HashSet(int initialCapacity, fload loadFactor) конструише празан хеш-сет задатог капацитета и load factor-а (број између 0.0 и 1.0 који одређује при ком проценту попуњености ће хеш-табела бити рехеширана у већу). java.lang.Object int hashCode() враћа хеш-кôд објекта. Хеш-кôд може бити произвољан цео број, позитиван или негативан. Дефиниције метода equals() и hashCode() морају бити компатибилне: ако је x.equals(y) true, онда x.hashCode()мора бити иста вредност као и y.hashCode(). Tree Sets Класа TreeSet је слична са хеш-сет, са додатим побољшањем. TreeSet је сортирана колекција. Елементи се убацују у колекцију у произвољном реду. Приликом итерирања кроз колекцију, вредности се аутоматски приказују у сортираном поретку. Нпр. претпоставимо да убацимо три стринга, а затим пролазимо кроз елементе које смо додали:

SortedSet<String> sorter = new TreeSet<String>(); // TreeSet implementira SortedSet sorter.add("Bob");

Page 20: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

20

sorter.add("Amy"); sorter.add("Carl"); for(String s : sorter) System.out.println(s);

Вредности се штампају у сортираном поретку: Amy, Bob, Carl. Као што име класе указује, сортирање је постигнуто коришћењем стабла као структуре података. (Тренутна имплементација користи red-black tree. За детаљан опис ових стабала, погледајте, нпр. Introduction to Algorithms аутора Thomas Cormen, Charles Leiserson, Ronald Rivest, Clifford Stein [The MIT Press, 2001].) Сваки пут када се елемент дода у стабло, бива смештен на одговарајућу позицију у складу са сортирањем. Према томе, итератор увек обилази елементе у сортираном поретку. Додавање елемента у стабло је спорије од додавања у хеш-табелу, али је још увек много брже од додавања на праву позицију у низу или повезаној листи. Ако стабло садржи n елемената, потребно је у просеку log2 n поређења за проналажење исправне позиције новог елемента. Нпр. ако стабло већ садржи 1000 елемената, додавање новог елемента захтева око 10 поређења. Према томе, додавање елемената у TreeSet је донекле спорије од додавања у HashSet - видети табелу која следи - али TreeSet аутоматски сортира елементе. Табела - Додавање елемената у HashSet и TreeSet Документ Укупан број речи Број различитих речи HashSet TreeSet Alice in Wonderland 28195 5909 5 sec 7 sec The Count of Monte Cristo 466300 37545 75 sec 98 sec java.util.TreeSet<E> TreeSet() конструише празан tree set. TreeSet(Collection<? extends E> elements) конструише tree set и додаје у њега елементе из колекције. Поређење објеката Како TreeSet „зна“ начин на који ми желимо да елементи буду сортирани? Подразумевано, tree set претпоставља да убацујемо елементе класе која имплементира интерфејс Comparable. Тај интерфејс дефинише само један метод:

public interface Comparable<T>{ public int compareTo(T other); }

Позив a.compareTo(b) мора вратити 0 ако су а и b једнаки, негативан цео број ако је а испред b у сортираном поретку, а позитиван цео број ако је а после b. Тачна вредност није важна, већ само њен знак (>0, 0 или <0). Неколико стандардних Јава класа имплементира интерфејс Comparable. Једна од њих је класа String. Њен метод compareTo() пореди стрингове лексикографски.

Page 21: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

21

Ако убацујемо сопствене објекте, морамо сами дефинисати поредак имплементирањем интерфејса Comparable. Не постоји подразумевана имплементација метода compareTo()у класи Object. Нпр. овако се могу сортирати објекти класе Artikal по броју артикла (brojArtikla је типа int):

class Artikal implements Comparable<Artikal>{ public int compareTo(Artikal a){ return brojArtikla - a.brojArtikla; } }

Ако поредимо два позитивна цела броја, попут бројева артикала из нашег примера, можемо просто вратити њихову разлику - она ће бити негативна ако први артикал треба да дође пре другог, 0 ако су бројеви артикала идентични, а позитивна иначе. ОПРЕЗ: Овај трик функционише једино ако су бројеви из довољно малог опсега. Ако је x велики позитиван цео број, а y велики негативан број, разлика x-y може изаћи изван опсега целих бројева (енг. overflow). Коришћење интерфејса Comparable за дефинисање поретка има очигледна ограничења. Дата класа може имплементирати интерфејс само једном. Шта се може предузети ако је потребно сортирати артикле по броју артикла у једној колекцији, а по опису у другој? Додатно, шта се може предузети ако је потребно сортирати објекте класе чији креатор није имплементирао интерфејс Comparable? У таквим ситуацијама „кажемо“ tree set-у да користи другачији метод за поређење прослеђивањем објекта класе која имплементира интерфејс Comparator конструктору класе TreeSet. Интерфејс Comparator декларише метод compare() са два експлицитна параметра:

public interface Comparator<T>{ int compare(T a, T b); }

Попут метода compareTo(), метод compare() враћа негативан цео број ако је а испред b у сортираном поретку, 0 ако су идентични, а позитиван цео број иначе. Како би се артикли сортирали по опису, просто дефинишемо класу која имплементира интерфејс Comparator:

class ArtikalComparator implements Comparator<Artikal>{ public int compare(Artikal a, Artikal b){ String opisA = a.getOpis(); String opisB = b.getOpis(); return opisA.compareTo(opisB); } }

Page 22: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

22

Затим проследимо објекат ове класе конструктору tree set-a: ArtikalComparator comp = new ArtikalComparator(); SortedSet<Artikal> sortPoOpisu = new TreeSet<Artikal>(comp); Ако конструишемо стабло са компаратором, оно користи овај објекат кад год треба да пореди два елемента. Приметимо да овај компаратор не поседује никакве податке, има само метод за поређење. Такав објекат се понекад назива „објекат-функција“. Објекти-функције се уобичајено дефинишу „у лету“, као инстанце анонимних унутрашњих класа:

SortedSet<Artikal> sortPoOpisu = new TreeSet<Artikal>( new Comparator<Artikal>(){ public int compare(Artikal a, Artikal b){ String opisA = a.getOpis(); String opisB = b.getOpis(); return opisA.compareTo(opisB); } } );

Заправо, интерфејс Comparator<T> декларисан је тако да има два метода: compare() и equals(). Наравно, свака класа поседује метод equals(), па делује да је мало користи од његовог додавања у декларацију овог интерфејса. Документација објашњава да нема потребе за предефинисањем метода equals(), али да у неким случајевима то води побољшању перформанси. Нпр. метод addAll() класе TreeSet може бити ефикаснији ако додајемо елементе из другог скупа који користи исти компаратор. Ако погледамо претходну табелу, можемо се запитати треба ли увек користити tree set уместо hash set-a? После свега, не делује да додавање елемената траје много дуже, а елементи су аутоматски сортирани. Одговор зависи од података које прикупљамо. Ако не желимо да буду сортирани, нема разлога да плаћамо цену сортирања. Још важније, за неке податке је много теже изаћи на крај са поретком него са хеш-функцијом. Хеш-функција само треба разумно да разбацује објекте, док функција поређења мора да распозна објекте са потпуном прецизношћу. Како би се ова разлика учинила конкретнијом, размотримо прикупљање скупа правоугаоника. Ако користимо TreeSet, неопходно је да обезбедимо Comparator<Pravougaonik>. Како поредимо два правоугаоника? По површини? То „не иде“. Можемо имати два различита правоугаоника, са различитим координатама темена, али једнаке површине. Поредак за стабло мора бити потпун (total ordering). Произвољна два елемента морају бити упоредива, а поређење може дати 0 само ако су елементи једнаки. Постоји такав поредак за правоугаонике (лексикографско уређење по координатама), али је неприродан и гломазан за израчунавање. Насупрот томе, хеш-функција је скоро дефинисана за класу Pravougaonik. Она просто хешира координате. Почев од Java SE 6 класа TreeSet имплементира интерфејс NavigableSet. Овај интерфејс додаје неколико метода погодних за лоцирање елемената и обилазак уназад.

Page 23: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

23

Пример 3 (TreeSetTest): Гради 2 tree set-a Artikal објеката. Први сортира по броју артикла, што је подразумевани поредак Artikal објеката. Други сортира по опису користећи компаратор. java.lang.Comparable<T> int compareTo(T other) пореди текући и објекат other и враћа негативну вредност ако текући објекат долази испред other, 0 ако су идентични у сортираном поретку, а позитивну вредност иначе. java.util.Comparator<T> int compare(T a, T b) пореди два објекта и враћа негативну вредноста ако a долази испред b, 0 ако су идентични у сортираном поретку, а позитивну вредност иначе. java.util.SortedSet<E> Comparator<? super E> comparator() враћа компаратор који се користи за сортирање елемената или null ако се елементи пореде методом compareTo() интерфејса Comparable. E first() E last() враћа најмањи или највећи елемент сортираног скупа. java.util.NavigableSet<E> E higher(E value) E lower(E value) враћа најмањи елемент >value или највећи елемент <value или null ако такав елемент не постоји. E ceiling(E value) E floor(E value) враћа најмањи елемент >=value или највећи елемент <=value или null ако такав елемент не постоји. E pollFirst() E pollLast() уклања и враћа најмањи или највећи елемент скупа или null ако је скуп празан. Iterator<E> descendingIterator() враћа итератор који обилази скуп у опадајућем смеру.

Page 24: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

24

java.util.TreeSet<E> TreeSet() конструише tree set за смештање Comparable објеката. TreeSet(Comparator<? super E> c) конструише tree set и користи задати компаратор за сортирање његових елемената. TreeSet(SortedSet<? extends E> elements) конструише tree set, додаје у њега све елементе сортираног скупа и користи исти компаратор као и дати сортирани скуп. Queues and Deques Kao што је већ речено, ред омогућује ефикасно додавање елемената на крај и уклањање елемената са почетка. Ред са два краја, тзв. deque, омогућује ефикасно додавање и уклањање елемената и са почетка и са краја. Додавање елемената у средину није подржано. Java SE 6 уводи интерфејс Deque. Имплементирају га класе ArrayDeque и LinkedList, при чему обе обезбеђују колекције чија величина расте по потреби. java.util.Queue<E> boolean add(E element) boolean offer(E element) додаје дати елемент на крај и враћа true када ред није пун. Ако је ред пун, први метод избацује IllegalStateException, а други враћа false. E remove() E poll() уклања и враћа елемент са почетка реда када ред није празан. Ако је ред празан, први метод избацује NoSuchElementException, а други враћа null. E element() E peek() враћа елемент са почетка реда не уклањајући га када ред није празан. Ако је ред празан, први елемент избацује NoSuchElementException, а други враћа null. java.util.Deque<E> void addFirst(E element) void addLast(E element) boolean offerFirst(E element) boolean offerLast(E element) додаје дати елемент на почетак или крај када ред није пун. Ако је ред пун, прва два метода избацују IllegalStateException, а последња два враћају false.

Page 25: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

25

E removeFirst() E removeLast() E pollFirst() E pollLast() уклања и враћа елемент са почетка или краја када ред није празан. Ако је ред празан, прва два метода избацују NoSuchElementException, а последња два враћају null. E getFirst() E getLast() E peekFirst() E peekLast() враћа елемент са почетка или краја реда не уклањајући га када ред није празан. Ако је ред празан, прва два метода избацују NoSuchElementException, а последња два враћају null. java.util.ArrayDeque<E> ArrayDeque() ArrayDeque(int initialCapacity) конструише неограничени deque иницијалног капацитета 16 или задатог иницијалног капацитета. Priority Queues Ред са приоритетом дохвата елементе у сортираном поретку након што су убачени произвољним редом. Кад год се позове метод remove(), добије се тренутно најмањи елемент у реду. Међутим, ред са приоритетом не сортира све своје елементе. Ако се итерира кроз елементе, они нису нужно сортирани. Ред са приоритетом користи елегантну и ефикасну структуру података, хип (енг. heap). Хип је бинарно стабло у ком операције add() и remove() узрокују да најмањи елемент гравитира ка врху без губљења времена на сортирање свих елемената. Попут TreeSet, ред са приоритетом може чувати или елементе класе која имплементира интерфејс Comparable или се Comparator објекат прослеђује конструктору. Типична употреба реда са приоритетом је за распоређивање послова (енг. job scheduling). Сваки посао има приоритет. Послови се додају произвољним редом. Кад год треба започети нови посао, из реда се уклања посао највишег приоритета. (Како је традиционално приоритет 1 „највиши“ приоритет, операција remove() уклања најмањи елемент.) Пример 4 (PriorityQueueTest): За разлику од итерирања у TreeSet, итерирање овде не посећује елементе у сортираном поретку. Ипак, уклањање увек води најмањем преосталом елементу. java.util.PriorityQueue PriorityQueue() PriorityQueue(int initialCapacity) конструише ред са приоритетом за смештање Comparable објеката. PriorityQueue(int initialCapacity, Comparator<? super E> c) конструише ред са приоритетом и користи задати компаратор за сортирање његових елемената.

Page 26: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

26

Мапе Скуп је колекција која омогућује брзо проналажење постојећег елемента. Међутим, за тражење елемента неопходно је имати његову тачну копију. То није веома уобичајено приликом претраге - обично имамо неку кључну информацију и желимо да пронађемо елемент који јој је придружен. Мапа је структура података која управо томе служи. У мапу се смештају парови кључ/вредност. Можемо наћи вредност ако имамо кључ. Нпр. можемо чувати табелу записа о запосленима, где су кључеви ID запослених, а вредности објекти класе Radnik. У Јавиној библиотеци налазе се две имплементације мапа опште намене: HashMap и TreeMap. Обе ове класе имплементирају интерфејс Map. Hash map хешира кључеве, а tree map користи тотално уређење кључева како би их организовала у стабло претраге. Хеш или функција поређења примењује се искључиво на кључеве. Вредности придружене кључевима се не хеширају нити се пореде. Да ли се определити за hash map или tree map? Као и са скуповима, хеширање је за нијансу брже и пожељније је ако није неопходан обилазак кључева у сортираном поретку. Следи пример коришћења хеш-мапе за смештање информација о запосленима: Map<String, Radnik> osoblje = new HashMap<String, Radnik>(); // HashMap implementira Map Radnik hari = new Radnik("Hari Haker"); osoblje.put("987-98-9996", hari); … Кад год се објекат додаје у мапу, неопходно је проследити и кључ. У нашем случају, кључ је стринг, а одговарајућа вредност је објекат класе Radnik. За дохватање објекта мора се користити кључ:

String s = "987-98-9996"; r = osoblje.get(s); // vraca hari

Ако задати кључ није претходно коришћен за смештање информација у мапу, метод get() враћа null. Кључеви морају бити јединствени. Не могу се у мапу сместити две вредности коришћењем истог кључа. Ако се метод put() позове два пута са истим кључем, друга вредност замењује прву. Заправо, метод put() враћа вредност претходно смештену коришћењем кључа који му се проследи као први аргумент. Метод remove() уклања елемент са датим кључем из мапе. Метод size() враћа број уноса у мапи. Мапа се не сматра колекцијом сама по себи. Међутим, могуће је добити тзв. погледе на мапу (енг. view), тј. објекте који имплементирају интерфејс Collection или неки од његових подинтерфејса.

Page 27: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

27

Постоје три погледа: скуп кључева, колекција вредности (која није скуп) и скуп парова кључ/вредност. Кључеви и парови кључ/вредност чине скупове јер у мапи може да постоји само једна копија кључа. Методи: Set<K> keySet() Collection<V> values() Set<Map.Entry<K, V>> entrySet() враћају ова три погледа. (Елементи скупа уноса су објекти статичке унутрашње класе Map.Entry) Приметимо да скуп кључева није HashSet нити TreeSet, већ објекат неке друге класе која имплементира интерфејс Set. Интерфејс Set изведен је из интерфејса Collection. Према томе, скуп кључева се може користити као и било која друга колекција. Нпр. може се проћи кроз све кључеве мапе: Set<String> kljucevi = mapa.keySet(); for(String kljuc : kljucevi){ radi se nesto sa kljucem kljuc } САВЕТ: Ако желите да видите и кључеве и вредности, користите кôд попут: for(Map.Entry<String, Radnik> unos : osoblje.entrySet() ){ String kljuc = unos.getKey(); Radnik vrednost = unos.getValue(); radi se nesto sa kljuc, vrednost } Ако позовете метод remove() за итератор, заправо уклањате из мапе кључ и њему придружену вредност. Није могуће, међутим, додати елемент у поглед скупа кључева. Нема смисла додати кључ, а не додати и вредност. Ако се позове метод add(), он избацује UnsupportedOperationException. Поглед скупа уноса има исто ограничење, иако концептулно има смисла додати нови пар кључ/вредност. Пример 5 (MapTest): Најпре се у мапу додају парови кључ/вредност. Затим се уклања један кључ, што такође уклања и њему придружену вредност. Онда се мења вредност придружена кључу и позива метод get() да се очита вредност. Најзад, пролази се кроз скуп уноса. java.util.Map<K, V> V get(K key) дохвата вредност придружену задатом кључу; враћа објекат придружем кључу или null ако кључ није нађен у мапи. Кључ може бити null. V put(K key, V value) ставља задати пар кључ/вредност у мапу. Ако је кључ већ присутан у мапи, нови објекат замењује стари, претходно придружен кључу. Метод враћа стару вредност или null ако кључ није био присутан у мапи. Кључ може бити null, али вредност не сме бити null.

Page 28: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

28

void putAll(Map<? extends K, ? extends V> entries) додаје све уносе из задате у текућу мапу. boolean containsKey(Object key) враћа true ако је кључ присутан у мапи. boolean containsValue(Object value) враћа true ако је вредност присутна у мапи. Set<Map.Entry<K, V>> entrySet() враћа поглед који чини скуп Map.Entry објеката, тј. парова кључ/вредност из мапе. Могуће је уклањати елементе из овог скупа и они се уклањају и из мапе, али није могуће додавати елементе. Set<K> keySet() враћа поглед који чини скуп свих кључева мапе. Могуће је уклањати елементе из овог скупа, чиме се из мапе уклањају и кључеви и њима придружене вредности, али није могуће додавати елементе у скуп. Collection<V> values() враћа поглед који чини колекција свих вредности из мапе. Могуће је уклањати вредности из ове колекције чиме се уклоњена вредност и њен кључ уклањају из мапе, али није могуће додавати елементе у колекцију. java.util.Map.Entry K getKey() V getValue() враћа кључ или вредност текућег уноса. V setValue(V newValue) мења вредност у придруженој мапи на нову и враћа стару вредност. java.util.HashMap<K, V> HashMap() HashMap(int initialCapacity) HashMap(int initialCapacity, float loadFactor) конструише празну хеш-мапу задатог капацитета и load factor-а (број између 0.0 и 1.0 који одређује при ком проценту попуњености се хеш-табела рехешира у већу). Подразумевани је load factor 0.75. java.util.TreeMap TreeMap(Comparator<? super K> c) конструише tree map и користи задати компаратор за сортирање кључева. TreeMap(Map<? extends K, ? extends V> entries) конструише tree map и додаје све уносе из задате мапе.

Page 29: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

29

TreeMap(SortedMap<? extends K, ? extends V> entries) конструише tree map, додаје све уносе из задате сортиране мапе и користи исти компаратор као и дата сортирана мапа. java.util.SortedMap<K, V> Comparator<? super K> comparator() враћа компаратор који се користи за сортирање кључева или null ако се кључеви пореде матодом compareTo()интерфејса Comparable. K firstKey() K lastKey() враћа најмањи или највећи кључ из мапе. Специјализоване класе скупова и мапа У библиотеци се налази и неколико класа за специјализоване потребе, које су укратко описане у овој секцији. Weak Hash Maps Класа WeakHashMap је дизајнирана да реши занимљив проблем. Шта се дешава са вредношћу чији кључ се не користи више нигде у програму? Претпоставимо да је последња референца на кључ нестала. Тада нема више начина за реферисање на објекат-вредност, али како ниједан део програма нема више кључ, пар кључ/вредност не може бити уклоњен из мапе. Зашто га garbage collector не може уклонити? Зар није његов посао да уклања објекте који се не користе? На жалост, ствари нису тако просте. Garbage collector прати „живе“ објекте. Све док је мапа-објекат жив, сви bucket-и у њему су живи и garbage collector их неће скупљати. Према томе, наш програм треба да се побрине да уклања некоришћене вредности из „дуговечних“ мапа. Уместо тога можемо користити WeakHashMap. Ова структура података сарађује са garbage collector-ом како би уклањала парове кључ/вредност када је једина референца на кључ она из хеш-табеле. Овако изнутра функционише тај механизам. WeakHashMap користи слабе референце (енг. weak references) за чување кључева. Објекат класе WeakReference чува референцу на други објекат, у нашем случају, на кључ хеш-табеле. Garbage collector третира објекте овог типа на посебан начин. Нормално, ако garbage collector открије да нема референци на неки објекат, просто покупи тај објекат. Међутим, ако је објекат доступан само преко WeakReference, такође га покупи, али смешта слабу референцу која је водила до њега у ред. Операције класе WeakHashMap периодично проверавају тај ред у потрази за новопристиглим слабим референцама. Пристизање нове слабе референце у ред означава да одговарајући кључ више нико не користи и да га је collector покупио. WeakHashMap тада уклања придружени унос.

Page 30: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

30

Linked Hash Sets and Maps У Java SE 1.4 додате су класе LinkedHashSet и LinkedHashMap, које памте редослед у ком су елементи додавани. На тај начин избегава се наизглед случајан поредак елемената хеш-табеле. Како се уноси умећу у табелу, тако се придружују двоструко повезаној листи:

Нпр. размотримо следећа додавања у мапу: Map<String, Radnik> osoblje = new LinkedHashMap<String, Radnik>(); osoblje.put("144-25-5464", new Radnik("Cak Noris")); osoblje.put("567-24-2546", new Radnik("Harison Ford")); osoblje.put("157-62-7935", new Radnik("Gari Kuper")); osoblje.put("456-62-5527", new Radnik("Demi Kucer")); Тада osoblje.keySet().iterator() обилази кључеве следећим редом: 144-25-5464 567-24-2546 157-62-7935 456-62-5527 а osoblje.values().iterator() обилази вредности следећим редом: Cak Noris Harison Ford Gari Kuper Demi Kucer

Page 31: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

31

LinkedHashMap алтернативно може користити редослед приступа (енг. access order) уместо редоследа додавања за итерирање кроз уносе мапе. Сваки пут када се позове get() или put(), унос на који се метод односи се уклања са своје текуће позиције у повезаној листи уноса и смешта на крај те листе. (Само позиција у повезаној листи уноса се мења, не и bucket хеш-табеле. Унос увек остаје у bucket-у који одговара хеш-вредности његовог кључа.) За конструисање такве хеш-мапе служи позив облика: LinkedHashMap<K, V>(initialCapacity, loadFactor, true) Редослед приступа је користан за имплементирање „least recently used“ политике за кеш. Нпр. можда желимо да чувамо уносе којима се често приступа у меморији, а оне уносе којима се ређе приступа да учитавамо из базе података. Када не пронађемо унос у табели, а табела је већ прилично напуњена, можемо добити итератор за табелу и уклонити први елемент који он обиђе. Такви уноси су најмање скоро коришћени (least recently used). Овај процес се чак може и аутоматизовати. Изведе се класа из LinkedHashMap и предефинише (енг. override) метод protected boolean removeEldestEntry(Map.Entry<K, V> eldest) Додавање новог уноса тада узрокује да најстарији унос (eldest) буде уклоњен кад год наш метод врати true. Нпр. величина следећег кеша је увек највише 100 (елемената): Map<K, V> cache = new LinkedHashMap<K, V>(128, 0.75F, true){ protected boolean removeEldestEntry(Map.Entry<K, V> eldest){ return size() > 100; } } Алтернативно, може се размотрити унос eldest како би се одлучило да ли га уклонити или не. Нпр. можда желимо да проверимо временску ознаку (енг. time stamp) смештену у унос. Enumeration Sets and Maps EnumSet је ефикасна имплементација скупа са елементима који припадају типу енумерације. Како тип енумерације има коначан број инстанци, EnumSet је интерно имплементиран просто као секвенца битова. Бит је постављен ако је одговарајућа вредност присутна у скупу. Класа EnumSet нема јавних конструктора. За конструисање скупа користи се статички factory метод: enum Dani {PONEDELJAK, UTORAK, SREDA, CETVRTAK, PETAK, SUBOTA, NEDELJA}; EnumSet<Dani> uvek = EnumSet.allOf(Dani.class); EnumSet<Dani> nikada = EnumSet.noneOf(Dani.class); EnumSet<Dani> radni = EnumSet.range(Dani.PONEDELJAK, Dani.PETAK); EnumSet<Dani> ponsrepet = EnumSet.of(Dani.PONEDELJAK, Dani.SREDA, Dani.PETAK);

За модификовање EnumSet могу се користити уобичајени методи интерфејса Set.

Page 32: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

32

EnumMap је мапа са кључевима који припадају типу енумерације. Она је просто и ефикасно имплементирана као низ вредности. Потребно је задати тип кључева у конструктору: EnumMap<Dani, Radnik> dezurni = new EnumMap<Dani, Radnik>(Dani.class);

ПРИМЕДБА: E extends Enum<E> у документацији за EnumSet просто значи „Е је типа енумерације“. Сви енумерисани типови изведени су из генеричке класе Enum. Нпр. Dani extends Enum<Dani>. Identity Hash Maps У Java SE 1.4 додата је и класа IdentityHashMap за једну прилично специјализовану употребу, где се хеш-вредности кључева не рачунају помоћу метода hashCode(), већ методом System.identityHashCode(). То је метод кога метод hashCode() класе Object користи за израчунавање хеш-кôда на основу меморијске адресе објекта. Такође, за поређење објеката IdentityHashMap користи ==, а не метод equals(). Другим речима, различити објекти кључеви сматрају се различитим чак и ако имају исти садржај. Ова класа је корисна за имплементирање алгоритама за обилажење објеката (попут серијализације) у којима желимо да пратимо који објекти су већ обиђени. java.util.WeakHashMap<K, V> WeakHashMap() WeakHashMap(int initialCapacity) WeakHashMap(int initialCapacity, float loadFactor) конструише празну хеш-мапу задатог капацитета и load factor-а. java.util.LinkedHashSet LinkedHashSet() LinkedHashSet(int initialCapacity) LinkedHashSet(int initialCapacity, float loadFactor) конструише празан linked hash set задатог капацитета и load factor-а. java.util.LinkedHashMap LinkedHashMap() LinkedHashMap(int initialCapacity) LinkedHashMap(int initialCapacity, float loadFactor) LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) конструише празну linked hash map задатог капацитета и load factor-а и редоследа. Параметар accessOrder је true за редослед приступа, а false за редослед додавања. protected boolean removeEldestEntry(Map.Entry<K, V> eldest) треба га предефинисати тако да враћа true ако унос eldest треба да буде уклоњен. Параметар eldest је унос чије се уклањање очекује. Метод се позива након додавања новог уноса у мапу. Подразумевана имплементација враћа false - стари елементи се подразумевано не уклањају. Међутим, могуће је редефинисати метод тако да селективно враћа true, нпр. ако најстарији унос испуњава одређени услов или мапа премаши одређену величину.

Page 33: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

33

java.util.EnumSet<E extends Enum<E>> static <E extends Enum<E>> EnumSet<E> allOf(Class<E> enumType) враћа скуп који садржи све вредности датог типа енумерације. static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> enumType) враћа празан скуп који може да садржи вредности датог типа енумерације. static <E extends Enum<E>> EnumSet<E> range(E from, E to) враћа скуп који садржи све вредности између from и to, укључујући и њих. static <E extends Enum<E>> EnumSet<E> of(E value) static <E extends Enum<E>> EnumSet<E> of(E value, E… values) враћа скуп који садржи дате вредности. java.util.EnumMap<K extends Enum<K>, V> EnumMap(Class<K> keyType) констуише празну мапу чији су кључеви датог типа java.util.IdentityHashMap<K, V> IdentityHashMap() IdentityHashMap(int expectedMaxSize) конструише празну identity hash map чији је капацитет најмањи степен двојке већи од 1.5 * expectedMaxSize. Подразумевана вредност за expectedMaxSize је 21. java.lang.System static int identityHashCode(Object obj) враћа исти хеш-код (изведен из меморијске адресе објекта) који израчунава метод hashCode() класе Object, чак и када класа којој припада obj предефинише метод hashCode(). The Collections Framework Framework је скуп класа које формирају основу за изградњу напредне функционалности. Framework садржи суперкласе са корисном функционалношћу, политикама и механизмима. Корисник framework-а пише њихове поткласе како би проширио ту функционалност не морајући изнова да измишља основне механизме. Нпр. Swing је framework за корисничке интерфејсе. Јавина библиотека колекција чини framework за колекцијске класе. Она дефинише известан број интерфејса и апстрактних класа за имплементаторе колекција (слика) и прописује одређене механизме, попут протокола за итерирање. Могуће је користити колекцијске класе без много знања о framework-у, што је већ и рађено у претходним секцијама. Међутим, ако желите да имплементирате генеричке алгоритме који раде са већим бројем колекцијских типова или желите да додате нови колекцијски тип, разумевање framework-a је од помоћи.

Page 34: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

34

Постоје два фундаментална интерфејса за колекције: Collection и Map. Елементи се додају у колекцију методом: boolean add(E element) Међутим, мапе чувају парове кључ/вредност, који се у мапу стављају методом put(): V put(K key, V value) Како би се прочитали елементи колекције, она се обилази итератором. Међутим, за читање вредности из мапе користи се метод get(): V get(K key) List je уређена колекција. Елементи се додају на одређену позицију унутар контејнера. Објекат може бити смештен на своју позицију на два начина: помоћу целобројног индекса и помоћу list iterator-а. Интерфејс List дефинише методе случајног приступа: void add(int index, E element) E get(int index) void remove(int index) Као што је већ речено, интерфејс List обезбеђује ове методе случајног приступа без обзира на то да ли су они ефикасни или не за одређену имплементацију. Како би се избегло извршавање скупих операција случајног приступа, Java SE 1.4 уводи интерфејс RandomAccess. То је интерфејс

Page 35: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

35

без метода који се може користити за тестирање да ли одређена колекција подржава ефикасан случајан приступ: if (c instanceof RandomAccess) { koristite algoritam slucajnog pristupa } else { koristite algoritam sekvencijalnog pristupa } Класе ArrayList и Vector имплементирају интерфејс RandomAccess. Са теоретске тачке гледишта имало би смисла да постоји посебан интерфејс Array који је изведен из интерфејса List и декларише методе случајног приступа. Да постоји такав интерфејс Array, алгоритми који захтевају случајан приступ користили би параметре типа Array и не би се могло десити да их случајно применимо на колекције са спорим случајним приступом. Међутим, дизајнери framework-a изабрали су да не дефинишу посебан интерфејс јер су желели да одрже број интерфејса у библиотеци малим. Можете да проследите повезану листу алгоритмима који користе случајан приступ - само треба да будете свесни утицаја који то има на перформансе. Интерфејс ListIterator дефинише метод за додавање елемента испред позиције итератора: void add(E element) За дохватање и уклањање елемената са одређене позиције, просто се користе методи next() и remove() интерфејса Iterator. Интерфејс Set је идентичан интерфејсу Collection, али понашање метода је ближе дефинисано. Метод add() за скуп треба да одбацује дупликате. Метод equals() за скуп треба да буде дефинисан тако да су два скупа идентична ако имају исте елементе, не нужно у истом поретку. Метод hashCode() треба да буде дефинисан тако да два скупа са истим елементима имају исти хеш-кôд. Зашто креирати посебан интерфејс када су потписи метода исти? Концептуално, нису све колекције скупови. Тиме што је Set учињен интерфејсом, програмерима је омогућено да пишу методе који прихватају само скупове. Интерфејси SortedSet и SortedMap откривају компаратор коришћен за сортирање и дефинишу методе за добијање погледа на подскупове колекција. Ти погледи биће разматрани у наредној секцији. Коначно, Java SE 6 уводи интерфејсе NavigableSet и NavigableMap који садрже додатне методе за претраживање и обилазак сортираних скупова и мапа. Класе TreeSet и TreeMap имплементирају ове интерфејсе. Сада се од интерфејса окрећемо класама које их имплементирају. Већ је речено да интерфејси имају врло мали број метода који се могу тривијално имплементирати. Апстрактне класе обезбеђују многе од имплементација:

Page 36: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

36

AbstractCollection AbstractList AbstractSequentialList AbstractSet AbstractQueue AbstractMap

Ако имплементирате сопствену колекцијску класу, вероватно желите да је изведете из једне од ових класа тако да можете да користите методе које су оне имплементирале. Јавина библиотека обезбеђује конкретне класе:

LinkedList ArrayList ArrayDeque HashSet TreeSet PriorityQueue HashMap TreeMap

Следећа слика приказује везе између ових класа:

Коначно, известан број „legacy“ контејнерских класа присутан је од настанка Јаве, пре него што је настао collections framework: Vector Stack Hashtable Properties

Page 37: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

37

Ове класе интегрисане су у collections framework (слика). О њима ће бити речи касније.

Views and Wrappers Гледајући претходне слике, рекло би се да има превише интерфејса и апстрактних класа како би се имплементирао скроман број конкретних класа. Међутим, слике не причају читаву причу. Коришћењем погледа (енг. view) могу се добити други објекти који имплементирају интерфејсе Collection и Map. Видели смо такав пример када смо користили метод keySet() за мапу. На први поглед делује да метод креира нови скуп, пуни га кључевима из мапе и враћа га. Међутим, то није случај. Метод keySet() враћа објекат класе која имплементира интерфејс Set и чији методи манипулишу оригиналном мапом. Таква колекција назива се погледом. Техника погледа има корисне примене у collections framework-у које ће бити описане у наредним секцијама. Lightweight Collection Wrappers Статички метод asList() класе Arrays враћа List омотач (енг. wrapper) за обичан Јава низ. Овај метод омогућује да се низ проследи методу који као аргумент очекује листу или колекцију. Нпр.

Karta[] spil = new Karta[52]; … List<Karta> listaKarata = Arrays.asList(spil);

Page 38: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

38

Враћени објекат није ArrayList. То је објекат поглед са методима get() и set() који приступају низу. Сви методи који би променили величину низа (попут метода add() и remove() придруженог итератора) избацују UnsupportedOperationException. Почев од Java SE 5.0 метод asList() је декларисан тако да има променљив број аргумената. Уместо прослеђивања низа, такође се могу проследити индивидуални елементи. Нпр.

List<String> imena = Arrays.asList("Amy", "Bob", "Carl"); Позив

Collections.nCopies(n, objekat) враћа непроменљиви објекат који имплементира интерфејс List и даје илузију да има n елемената од којих сваки изгледа као objekat. Нпр. следећи позив креира List који садржи 100 стрингова, свих постављених на "DEFAULT":

List<String> podesavanja = Collections.nCopies(100, "DEFAULT"); Цена смештања је веома мала - објекат се смешта само једанпут. Ово је симпатична примена технике погледа. Класа Collections садржи известан број utility метода са параметрима или повратним вредностима које су колекције. Не мешати је са интерфејсом Collection. Позив

Collections.singleton(objekat); враћа поглед-објекат који имплементира интерфејс Set (за разлику од ncopies(), који производи List). Враћени објекат имплементира непроменљиви скуп који садржи један објекат. Методи singletonList() и singletonMap() се понашају слично. Subranges За неке колекције могуће је формирати „подопсег“ погледе. Нпр. претпоставимо да имамо листу osoblje и желимо да издвојимо елементе од 10 до 19. Можемо користити метод subList()да добијемо поглед на подопсег листе:

List grupa2 = osoblje.subList(10, 20); Први индекс је укључујући, а други искључујући - управо као код параметара метода substring() класе String. На подопсег се могу примењивати произвољне операције и оне се аутоматски одражавају на листу. Нпр. можемо обрисати читав подопсег: grupa2.clear(); // redukovanje osoblja

Page 39: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

39

Елементи су аутоматски уклоњени из листе osoblje, a grupa2 је празнa. Приликом формирања подопсега за сортиране скупове и мапе користи се поредак за сортирање, а не позиција елемента. Интерфејс SortedSet декларише три метода:

SortedSet<E> subSet(E from, E to) SortedSet<E> headSet(E to) SortedSet<E> tailSet(from)

Ови методи враћају подскупове елемената који су већи или једнаки од from и строго мањи од to. За сортиране мапе, слични методи

SortedMap<K, V> subMap(K from, K to) SortedMap<K, V> headMap(K to) SortedMap<K, V> tailMap(K from)

враћају погледе на мапу који се састоје од свих уноса у којима кључеви припадају задатим опсезима. Интерфејс NavigableSet, уведен у Java SE 6, даје више контроле над операцијама за подопсеге. Могуће је задати да ли су границе укључене: NavigableSet<E> subSet(E from, boolean fromInclusive, E to, boolean toInclusive) NavigableSet<E> headSet(E to, boolean toInclusive) NavigableSet<E> tailSet(E from, boolean fromInclusive)

Unmodifiable Views Класа Collections поседује методе који производе непроменљиве погледе на колекције. Ови погледи додају runtime провере постојеће колекције. Ако се детектује покушај модификовања колекције, избацује се изузетак и колекција остаје нетакнута. Непроменљиви погледи добијају се методима:

Collections.unmodifiableCollection() Collections.unmodifiableList() Collections.unmodifiableSet() Collections.unmodifiableSortedSet() Collections.unmodifiableMap() Collections.unmodifiableSortedMap()

Сваки од метода има параметар типа интерфејса. Нпр. Collections.unmodifiableList() ради са ArrayList, LinkedList и произвољном другом класом која имплементира интерфејс List. Нпр. претпоставимо да желите да допустите неком делу свог кôда да погледа, али „не дира“, садржај колекције. Ево шта је потребно да урадите:

List<String> osoblje = new LinkedList<String>(); … pogledaj(new Collections.unmodifiableList(osoblje));

Page 40: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

40

Метод Collections.unmodifiableList() враћа објекат класе која имплементира интерфејс List. Његови приступни (енг. accessor) методи дохватају вредности из колекције osoblje. Наравно, метод pogledaj() може позивати све методе интерфејса List, не само приступне. Али сви мутатор-методи, као што је метод add(), предефинисани су тако да избаце изузетак типа UnsupportedOperationException уместо да проследе позив полазној колекцији. Непроменљиви поглед не чини саму колекцију непроменљивом. Колекција се још увек може модификовати преко своје оригиналне референце (osoblje, у нашем случају). Такође, још увек је могуће позивати мутатор-методе над елементима колекције. Како погледи омотавају интерфејс, а не стварни објекат колекцију, постоји приступ само оним методима који су дефинисани у интерфејсу. Нпр. класа LinkedList поседује погодне методе addFirst() и addLast(), који нису део интерфејса List. Ови методи нису доступни преко непроменљивог погледа. ОПРЕЗ: Метод unmodifiableCollection() (као и методи synchronizedCollection() и checkedCollection(), о којима ће бити речи касније) враћа колекцију чији метод equals() не позива метод equals() полазне колекције, већ наслеђује метод equals() класе Object, који само тестира да ли се ради о истом објекту у меморији. Ако претворите скуп или листу у (само) колекцију, не можете и даље тестирати једнакост садржаја. Поглед се понаша на овај начин јер тестирање једнакости није добро дефинисано на овом нивоу хијерархије. Погледи третирају метод hashCode() на исти начин. Међутим, објекти добијени са unmodifiableSet() и unmodifiableList() користе методе equals() и hashCode()полазне колекције. Synchronized Views Ако колекцији приступа већи број нити, неопходно је обезбедити да се колекција случајно не оштети. Нпр. било би катастрофално када би једна нит покушавала да дода унос у хеш-табелу док друга врши рехеширање елемената. Уместо имплементирања thread-safe колекцијских класа, дизајнери библиотеке користе механизам погледа како би регуларне колекције учинили thread-safe. Нпр. статички метод synchronizedMap() класе Collections може претворити произвољну мапу у Map са синхронизованим приступним методима:

Map<String, Radnik> mapa = Collections.synchronizedMap(new HashMap<String, Radnik>());

Сада је могуће приступати објекту map из већег броја нити. Методи попут get() и put() су серијализовани - сваки позив метода мора се завршити у потпуности пре него што друга нит може позвати други метод. Проблем синхронизације приступа структурама података детаљно се разматра у следећем, 14. поглављу. Checked Views У Java SE 5.0 додат је скуп „проверених“ погледа намењених подршци за отклањање проблема који могу настати са генеричким типовима. Могуће је „прошверцовати“ елементе погрешног типа у генеричку колекцију. Нпр.

Page 41: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

41

ArrayList<String> stringovi = new ArrayList<String>(); ArrayList samoLista = stringovi; // dobija se samo upozorenje, ne greska, // zbog kompatiblinosti sa legacy kodom samoLista.add(new Date()); // sada stringovi sadrzi Date objekat! Погрешна команда add() не открива се у време извршавања, већ се ClassCastException дешава касније када други део кôда позове get() и кастује резултат у String. Проверени поглед може детектовати овај проблем. Дефинишите безбедну листу на следећи начин:

List<String> bezbedniStringovi = Collections.checkedList(stringovi, String.class); Метод add() погледа проверава да ли убачени објекат припада датој класи и непосредно избацује ClassCastException ако то није случај. Предност је што се грешка пријављује на исправној локацији:

ArrayList samoLista = bezbedniStringovi; samoLista.add(new Date()); // proverena lista izbacuje ClassCastException

ОПРЕЗ: Проверени погледи су ограничени runtime проверама које ВМ може извршавати. Нпр. ако имате ArrayList<Par<String>>, не можете га заштитити од додавања Par<Date> јер ВМ поседује једну „raw“ класу Par (Детаљно објашњење налази се у поглављу 12 књиге). A Note on Optional Operations Поглед обично има неко ограничење - може бити само за читање, нема могућност да промени величину колекције или може подржавати уклањање, али не и додавање, као што је случај са погледом кључева мапе. Такав поглед избацује UnsupportedOperationException ако се покуша неодговарајућа операција. У документацији за колекцијске и итератор интерфејсе, многи методи су описани као „опционе операције“. То делује као да је у супротности са појмом интерфејса. Након свега, зар није сврха интерфејса да излиста методе које класа мора имплементирати? Заиста, гледано из теоретске перспективе, тако нешто је незадовољавајуће. Боље решење би било дизајнирати посебне интерфејсе за погледе који могу само да читају и погледе који не могу променити величину колекције. Међутим, то би утростручило број интерфејса, што су дизајнери библиотеке сматрали неприхватљивим. Треба ли користити технику „опционих“ метода у сопственом дизајну? Аутори књиге сматрају да не треба. Иако се колекције често користе, стил кодирања за њихову имплементацију није типичан за проблеме из других домена. Дизајнери библиотеке колекцијских класа морају да разеше посебно бруталан скуп конфликтних захтева. Корисници желе библиотеку која је лака за учење, погодна за употребу, потпуно генеричка, а у исто време ефикасна као и ручно кодирани алгоритми. Просто је немогуће симултано постићи све ове захтеве, чак ни прићи близу томе. Али, у својим сопственим проблемима тешко да ћете наићи на тако екстреман скуп услова. Требало би да можете да нађете решења која се не заснивају на екстремној мери „опционих“ операција интерфејса.

Page 42: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

42

java.util.Collections static <E> Collection unmodifiableCollection(Collection<E> c) static <E> List unmodifiableList(List<E> c) static <E> Set unmodifiableSet(Set<E> c) static <E> SortedSet unmodifiableSortedSet(SortedSet<E> c) static <K, V> Map unmodifiableMap(Map<K, V> c) static <K, V> SortedMap unmodifiableSortedMap(SortedMap<K, V> c) конструише погледе на колекцију чији мутатор методи избацују UnsupportedOperationException. static <E> Collection synchronizedCollection(Collection<E> c) static <E> List synchronizedList(List<E> c) static <E> Set synchronizedSet(Set<E> c) static <E> SortedSet synchronizedSortedSet(SortedSet<E> c) static <K, V> Map synchronizedMap(Map<K, V> c) static <K, V> SortedMap synchronizedSortedMap(SortedMap<K, V> c) конструише погледе на колекцију чији методи су синхронизовани. static <E> Collection checkedCollection(Collection<E> c, Class<E> elementType) static <E> List checkedList(List<E> c, Class<E> elementType) static <E> Set checkedSet(Set<E> c, Class<E> elementType) static <E> SortedSet checkedSortedSet(SortedSet<E> c, Class<E> elementType) static <K, V> Map checkedMap(Map<K, V> c, Class<K> keyType, Class<V> valueType) static <K, V> SortedMap checkedSortedMap(SortedMap<K, V> c, Class<K> keyType, Class<V> valueType) конструише погледе на колекцију чији методи избацују ClassCastException ако се убаци елемент погрешног типа. static <E> List<E> nCopies(int n, E value) static <E> Set<E> singleton(E value) конструише поглед на објекат као непроменљиву листу од n идентичних елемената или као скуп са једним елементом. java.util.Arrays static <E> List<E> asList(E… array) враћа поглед-листу на елементе низа који је (тај поглед) модификујућ, али није променљиве величине. java.util.List<E> List<E> subList(int firstIncluded, int firstExcluded) враћа поглед-листу елемената из задатог опсега позиција. java.util.SortedSet<E> SortedSet<E> subSet(E firstIncluded, E firstExcluded) SortedSet<E> headSet(E firstExcluded) SortedSet<E> tailSet(E firstIncluded) враћа поглед на елементе из опсега.

Page 43: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

43

java.util.NavigableSet<E> NavigableSet<E> subSet(E from, boolean fromIncluded, E to, boolean toIncluded) NavigableSet<E> headSet(E to, boolean toIncluded) NavigableSet<E> tailSet(E from, boolean fromIncluded) враћа поглед на елементе из опсега. Boolean флегови одређују да ли је граница укључена у поглед. java.util.SortedMap<K, V> SortedMap<K, V> subMap(K firstIncluded, K firstExcluded) SortedMap<K, V> headMap(K firstExcluded) SortedMap<K, V> tailMap(K firstIncluded) враћа поглед-мапу на уносе чији су кључеви унутар опсега. java.util.NavigableMap<K, V> NavigableMap<K, V> subMap(K from, boolean fromIncluded, K to, boolean toIncluded) NavigableMap<K, V> headMap(K to, boolean toIncluded) NavigableMap<K, V> tailMap(K from, boolean fromIncluded) враћа поглед-мапу на уносе чији су кључеви унутар опсега. Boolean флегови одређују да ли је граница укључена у поглед. Bulk Operations (масовне, групне операције) До сада је већина наших примера користила итератор за обилазак колекције, елемент по елемент. Међутим, често је могуће избећи итерирање коришћењем неке од „bulk“ операција из библиотеке. Претпоставимо да желимо да нађемо пресек два скупа, тј. њихове заједничке елементе. Прво направимо нови скуп који ће бити резултат. Set<String> rezultat = new HashSet<String>(a); Овде користимо чињеницу да свака колекција има конструктор чији је параметар друга колекција са иницијализационим вредностима. Потом користимо метод retainAll(): result.retainAll(b); Он задржава све елементе који се појављују и у b. Пресек је формиран без програмирања петље. Ова идеја се може даље развити тако што се bulk операција примени на поглед. Нпр. претпоставимо да имамо мапу која мапира ID запослених на запослене и имамо скуп ID-јева запослених које треба уклонити из мапе. Map<String, Radnik> osobljeMapa = …; Set<String> uPenzijuID = …;

Page 44: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

44

Просто се формира скуп кључева мапе и уклоне сви ID-јеви запослених које треба уклонити из мапе: osobljeMapa.keySet().removeAll(uPenzijuID); Како је скуп кључева поглед на мапу, кључеви и придружени запослени се аутоматски уклањају из мапе. Коришћењем подопсег-погледа могуће је ограничити bulk операције на подлисте и подскупове. Нпр. претпоставимо да желимо да додамо првих 10 елемената листе у други контејнер. Формирамо подлисту да изаберемо првих 10: premesteni.addAll(osoblje.subList(0, 10)); На подопсег се такође може применити мутирајућа операција: osoblje.subList(0, 10).clear(); Конверзије између колекција и низова Повремено су потребна превођења између традиционалних низова и модернијих колекција. Ако имамо низ и потребно је да га претворимо у колекцију, користимо Arrays.asList(). Нпр.

String[] vrednosti = …; HashSet<String> osoblje = new HashSet<String>(Arrays.asList(vrednosti));

Добијање низа од колекције је мало теже. Наравно, могуће је користити метод toArray(): Object[] vrednosti = osoblje.toArray(); Међутим, резултујући низ је низ објеката. Чак и ако знамо да наша колекција садржи објекте одређеног типа, не можемо користити кастовање: String[] vrednosti = (String[]) osoblje.toArray(); // Greska! Низ који враћа toArray()креиран је као Object[] и није могуће променити његов тип. Уместо тога, може се користити друга верзија метода toArray()којој се проследи низ дужине 0 типа који желимо. Враћени низ ће онда бити тог типа: String[] vrednosti = osoblje.toArray(new String[0]); Ако желимо, можемо конструисати низ тако да буде коректне величине: osoblje.toArray(new String[osoblje.size()]); У овом случају не креира се нови низ.

Page 45: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

45

Алгоритми Генерички колекцијски интерфејси имају огромну предност - потребно је имплементирати своје алгоритме само једанпут. Нпр. размотримо једноставан алгоритам за израчунавање максималног елемента колекције. Традиционално, програмери имплементирају тај алгоритам као петљу. Ево како се налази највећи елемент низа: if (a.length == 0) throw new NoSuchElementException(); T largest = a[0]; for (int i = 1; i < a.length; i++) if(largest.compareTo(a[i]) < 0) largest = a[i]; Наравно, за налажење максимума array list кôд изгледа незнатно другачије: if(v.size() == 0) throw new NoSuchElementException(); T largest = v.get(0); for (int i = 1; i < v.size(); i++) if(largest.compareTo(v.get(i)) < 0) largest = v.get(i); Шта је са повезаном листом? У повезаној листи немамо ефикасан случајан приступ, али можемо користити итератор: if (l.isEmpty()) throw new NoSuchElementException(); Iterator<T> iter = l.iterator(); T largest = iter.next(); while(iter.hasNext()){ T next = iter.next(); if(largest.compareTo(next) < 0) largest = next; } Ове петље су напорне за писање и склоне су грешкама. Да ли се извршава итерација више или мање? Да ли петље раде исправно када је контејнер празан? Када има само један елемент? Не желите да тестирате програм сваки пут, а такође не желите да имплементирате мноштво метода попут: static <T extends Comparable> T max(T[] a) static <T extends Comparable> T max(ArrayList<T> v) static <T extends Comparabla> T max(LinkedList<T> l) Ту на сцену ступају колекцијски интерфејси. Размишљајте о минималном колекцијском интерфејсу који је потребан за ефикасан алгоритам. Случајан приступ помоћу get() и set() је на вишем нивоу у „ланцу исхране“ него проста итерација. Као што смо видели, у израчунавању максималног елемента повезане листе не захтева се случајан приступ. Израчунавање максимума може се урадити просто итерирањем кроз елементе. Према томе, могуће је имплементирати метод max() тако да прихвата објекат произвољне класе која имплементира интерфејс Collection.

Page 46: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

46

public static <T extends Comparable> T max(Collection<T> c){ if (c.isEmpty()) throw new NoSuchElementException(); Iterator<T> iter = c.iterator(); T largest = iter.next(); while (iter.hasNext()) { T next = iter.next(); if (largest.compareTo(next) < 0) largest = next; } return largest; }

Сада можемо да рачунамо максимум повезане листе, array list и низа коришћењем једног метода. Ово је моћан концепт. У Јавиној библиотеци користи се у алгоритмима сортирања, бинарне претраге и још неким корисним алгоритмима. Сортирање и мешање Метод sort() класе Collections сортира колекцију која имплементира интерфејс List.

List<String> osoblje = new LinkedList<String>(); // popunjavanje kolekcije … Collections.sort(osoblje);

Овај метод претпоставља да класа елемената листе имплементира интерфејс Comparable. Ако желите да сортирате листу на неки други начин, можете проследити Comparator објекат као други аргумент:

Comparator<Artikal> artikalComparator = new Comparator<Artikal>(){ public int compare(Artikal a, Artikal b){ return a.brojArtikla - b.brojArtikla; } }; Collections.sort(artikli, artikalComparator);

Ако желите да сортирате листу у опадајућем поретку, користите статички метод Collections.reverseOrder() који враћа компаратор чији метод compare() враћа b.compareTo(a). Нпр.

Collections.sort(osoblje, Collections.reverseOrder()) сортира елементе листе osoblje у обрнутом поретку у односу на поредак одређен методом compareTo() класе елемената листе. Слично,

Collections.sort(artikli, Collections.reverseOreder(artikalComparator)) обрће поредак који одређује artikalComparator.

Page 47: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

47

Можете се запитати како метод sort() сортира листу? Типично, када погледате алгоритме са сортирање у књигама, они су презентовани за низове и користе случајан приступ елементима. Међутим, случајан приступ у листи може бити неефикасан. Листе се могу сортирати коришћењем merge sort алгоритма. Ипак, имплементација у Јави не ради то. Она просто прекопира све елементе у низ, сортира га користећи варијанту merge sort алгоритма, а затим копира сортирану секвенцу натраг у листу. Merge sort алгоритам коришћен у библиотеци је за нијансу спорији од quick sort, који је традиционални избор алгоритма опште намене за сортирање. Међутим, merge sort има једну велику предност: стабилан је, тј. не врши размену једнаких елемената. Зашто нам је битан поредак једнаких елемената? Следи уобичајени сценарио. Претпоставимо да имамо листу запослених која је већ сортирана по имену. Сада сортирамо по заради. Шта се дешава са запосленима који имају исту зараду? Када је сортирање стабилно, поредак по имену бива очуван. Другим речима, излаз је листа сортирана најпре по заради, а потом по имену. Како колекције не морају имплементирати све своје „опционе“ методе, сви методи који имају параметре типа колекција морају описати да ли је безбедно проследити колекцију алгоритму. Нпр. јасно је да не можемо проследити unmodifiableList методу sort(). Коју врсту листи можемо проследити? Према документацији, листа треба да буде таква да ју је могуће модификовати (modifiable), али не мора бити могуће мењање њене димензије (не мора да буде resizable). Ови термини дефинисани су на следећи начин: - Листа је modifiable ако подржава метод set(). - Листа је resizable ако подржава операције add() и remove(). Класа Collections поседује алгоритам shuffle који ради супротно од сортирања - случајно пермутује редослед елемената у листи. Нпр.

ArrayList<Karta> karte = …; Collections.shuffle(karte);

Ако проследимо листу која не имплементира интерфејс RandomAccess, метод shuffle() копира елементе у низ, промеша низ и ископира промешане елементе натраг у листу. Пример 6 (ShuffleTest): ArrayList се попуњава са 49 Integer објеката који садрже бројеве од 1 до 49. Листа се затим случајно промеша и одабере се првих 6 вредности из ње. Коначно, изабрани бројеви се сортирају и штампају. java.util.Collections static <T extends Comparable<? super T>> void sort(List<T> elements) static <T> void sort(List<T> elements, Comparator<? super T> c) сортира елементе листе користећи стабилни алгоритам. Временска сложеност алгоритма је O(n log n), где је n дужина листе. static void shuffle(List<?> elements) static void shuffle(List<?> elements, Random r) случајно меша елементе листе. Временска сложеност алгоритма је O(n a(n)), где је n дужина листе, а a(n) просечно време приступа елементу.

Page 48: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

48

static <T> Comparator<T> reverseOrder() враћа компаратор који сортира елементе у обрнутом поретку од поретка одређеног методом compareTo() интерфејса Comparable. static <T> Compator<T> reverseOrder(Comparator<T> comp) враћа компаратор који сортира елементе у обрнутом поретку од поретка одређеног компаратором comp. Бинарна претрага Да би се пронашао објекат у низу, нормално се обилазе сви елементи док се не пронађе одговарајући. Међутим, ако је низ сортиран, могуће је проверити да ли је средишњи елемент већи од оног који тражимо. Ако јесте, настављамо потрагу у првој половини низа, а иначе тражимо у другој половини. Тиме се проблем редукује на пола. Наставља се на исти начин. Нпр. ако низ има 1024 елемента, наћи ћемо елемент (или утврдити да таквог нема) након највише 10 корака, док би линеарна претрага у просеку трајала 512 корака ако је елемент присутан, а 1024 за утврђивање да га нема. Метод binarySearch() класе Collections имплементира овај алгоритам. Приметимо да колекција мора претходно бити сортирана или ће алгоритам вратити погрешан резултат. Како би се пронашао елемент, проследи се колекција (која мора имплементирати интерфејс List) и елемент који се тражи. Ако колекција није сортирана коришћењем метода compareTo() интерфејса Comparable, мора се проследити и одговаајући компаратор.

i = Collections.binarySearch(c, element); i = Collections.binarySearch(c, element, comparator);

Повратна вредност ≥0 означава индекс пронађеног елемента, тј. c.get(i) једнако је са element у складу са коришћеним поређењем. Ако је повратна вредност негативна, не постоји тражени елемент у колекцији. Међутим, повратна вредност се може користити за израчунавање позиције на коју треба уметнути element у колекцију како би она остала сортирана. Та позиција је:

insertionPoint = -i - 1; Није просто -i јер онда би вредност 0 била двосмислена. Другим речима, операција if ( i < 0) c.add(-i - 1, element); додаје елемент на исправну позицију. Како би имала смисла, бинарна претрага захтева случајан приступ. Уколико морате да итерирате елемент по елемент кроз половину повезане листе како бисте нашли средишњи елемент, изгубили сте сву предност бинарне претраге. Према томе, binarySearch() се своди на линеарну претрагу када му дате повезану листу. Метод binarySearch() проверава да ли листа која му је дата као аргумент имплементира интерфејс RandomAccess, па ако то јесте случај, примењује бинарну претрагу, а у супротном користи линеарну.

Page 49: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

49

java.util.Collections static <T extends Comprable<? super T>> int binarySearch(List<T> elements, T key) static <T> int binarySearch(List<T> elements, T key, Comparator<? super T> c) тражи кључ у сортираној листи, користећи линеарну претрагу ако elements не имплементира RandomAccess интерфејс, а бинарну у супротном. Временска сложеност алгоритма је O(a(n) log n), где је n дужина листе, а a(n) просечно време приступа елементу. Методи враћају или индекс кључа у листи или негативну вредност i ако кључ није присутан у листи. У том случају, кључ треба убацити у листу на позицију -i - 1 како би листа остала сортирана. Једноставни алгоритми Класа Collections садржи неколико једноставних, али корисних алгоритама. Међу њима је и пример са почетка ове секције, налажење максималне вредности у колекцији. Остали укључују копирање елемената из једне листе у другу, попуњавање контејнера константном вредношћу и обртање листе. Зашто обезбедити тако једноставне алгоритме у стандардној библиотеци? Свакако да их већина програмера може лако имплементирати једноставним петљама. Ови алгоритми чине живот лакшим програмеру који чита кôд. Када читате петљу коју је имплементирао неко други, неопходно је да дешифрујете његове намере. Када видите позив метода попут Collections.max(), истог тренутка знате шта тај кôд ради. java.util.Collections static <T extends Comparable<? super T>> T min(Collection<T> elements) static <T extends Comparable<? super T>> T max(Collection<T> elements) static <T> min(Collection<T> elements, Comparator<? super T> c) static <T> max(Collection<T> elements, Comparator<? super T> c) враћа најмањи или највећи елемент у колекцији (ограничења за типске параметре су упрошћена због једноставности). static <T> void copy(List<? super T> to, List<T> from) копира све елементе из изворне листе на исте позиције у одредишној листи. Одредишна листа мора бити дуга бар колико изворишна. static <T> void fill(List<? super T> l, T value) попуњава све позиције у листи истом вредношћу. static <T> boolean addAll(Collection<? super T> c, T… values) додаје све вредности у дату колекцију и враћа true ако је тиме колекција промењена. static <T> boolean replaceAll(List<T> l, T oldValue, T newValue) замењује све елементе једнаке старој вредности новом вредношћу. static int indexOfSubList(List<?> l, List<?> s) static int lastIndexOfSubList(List<?> l, List<?> s) враћа индекс прве или последње подлисте од l једнаке са s или -1 ако нема такве подлисте у l. Нпр. ако је l [s, t, a, r], а s [t, a, r], тада оба метода враћају индекс 1. static void swap(List<?> l, int i, int j) размењује елементе на датим позицијама.

Page 50: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

50

static void reverse(List<?> l) обрће редослед елемената у листи. Временска сложеност алгоритма је O(n), где је n дужина листе. static void rotate(List<?> l, int d) ротира елементе листе, померајући елемент са индексом i на позицију (i + d) % l.size(). Нпр. ротирањем листе [t, a, r] за 2 добија се листа [a, r, t]. Временска сложеност алгоритма је O(n), где је n дужина листе. static int frequency(Collection<?> c, Object o) враћа број елемената листе једнаких датом објекту. boolean disjoint(Collection<?> c1, Collection<?> c2) враћа true ако колекције немају заједничких елемената. Писање сопствених алгоритама Ако сами пишете алгоритам (или, заправо, произвољан метод који има колекцију као параметар), требало би да радите са интерфејсима уместо са конкретним имплементацијама кад год је то могуће. Нпр. претпоставимо да желите да попуните JМenu скупом ставки. Традиционално, такав метод могао би бити имплементиран на следећи начин:

void popuniMeni(JMenu meni, ArrayList<JMenuItem> stavke){ for(JMenuItem stavka : stavke) meni.add(stavka); }

Међутим, тиме сте условили позиваоца свог метода - он мора сместити ставке у ArrayList. Ако се деси да су оне већ у неком другом контејнеру, најпре се морају препаковати. Много је боље прихватити општију колекцију. Потребно је запитати се: Који је најопштији колекцијски интерфејс који се може употребити? У овом случају неопходно је само посетити све елементе, што је могућност коју пружа основни интерфејс Collection. Овако изгледа метод popuniMeni() преписан тако да прихвата произвољну врсту колекције:

void popuniMeni(JMenu meni, Collection<JMenuItem> stavke){ for(JMenuItem stavka : stavke) meni.add(stavka); }

Сада свако може позивати овај метод са ArrayList, LinkedList или чак са низом уз коришћење омотача Arrays.asList(). Ако је идеја коришћења колекцијских интерфејса као параметара метода тако добра, зашто је Јавина библиотека не користи чешће? Нпр. класа JComboBox има два конструктора: JComboBox(Object[] items) JComboBox(Vector<?> items) Разлог је просто време. Swing библиотека настала је пре библиотеке колекција.

Page 51: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

51

Ако пишете метод који враћа колекцију, такође можда желите да вратите интерфејс уместо класе јер се можете предомислити и касније реимплементирати метод користећи другу колекцију. Нпр. напишимо метод getStavke() који враћа све ставке менија.

List<MenuItem> getStavke(JMenu meni){ ArrayList<MenuItem> stavke = new ArrayList<MenuItem>(); for(int i = 0; i < meni.getItemCount(); i++) stavke.add(meni.getItem(i)); return stavke; }

Касније можете одлучити да не желите да копирате ставке, већ просто да обезбедите поглед на њих. Ово се може постићи враћањем објекта анонимне поткласе класе AbstractList.

List<MenuItem> getStavke(final JMenu meni) { return new AbstractList<MenuItem>() { public MenuItem get(int i){ return meni.getItem(i); } public int size(){ return meni.getItemCount(); } }; }

Наравно, ово је напредна техника. Ако је примените, пажљиво документујте које „опционе“ операције су подржане. У овом случају морате обавестити позиваоца да је враћени објекат непроменљива листа. Legacy Collections У овој секцији разматрају се колекцијске класе које постоје у програмском језику Јава од самог почетка: класа Hashtable и њена корисна поткласа Properties, поткласа Stack класе Vector и класа BitSet. Класа Hashtable Класа Hashtable има исту намену као и HashMap и има исти интерфејс. Попут метода класе Vector, методи класе Hashtable су синхронизовани. Ако Вам није потребна синхронизација, као ни компатибилност са legacy кодом, требало би да, уместо ове класе, користите класу HashMap. Име класе је Hashtable, са малим словом t.

Page 52: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

52

Енумерације Legacy колекције користе интерфејс Enumeration за обилажење секвенци елемената. Интерфејс Enumeration има два метода, hasMoreElements() и nextElement(). Они су потпуно аналогни са методима hasNext() и next() интерфејса Iterator. Нпр. метод elements() класе Hashtable враћа објекат за енумерисање вредности из табеле:

Enumeration<Radnik> r = osoblje.elements(); while (r.hasMoreElements()) { Radnik radnik = r.nextElement(); … }

Повремено ћете наићи на legacy метод који очекује параметар типа Enumeration. Статички метод Collections.enumeration() враћа Enumeration објекат који енумерише елементе колекције. Нпр.

List<InputStream> streams = …; SequenceInputStream in = new SequenceInputStream(Collections.enumeration(streams)); // konstruktor SequenceInputStream()ocekuje enumeraciju

У програмском језику C++ је прилично уобичајено користити итераторе као параметре метода. На срећу, у Јави веома мали број програмера тако ради. Много је паметније проследити колекцију него итератор. Објекат који представља колекцију је много кориснији. Прималац може увек да добије итератор од колекције када за тим има потребе, а , сем тога, има на располагању и све методе колекције. Међутим, наћи ћете енумерације у legacy кôду јер су оне биле једини доступан механизам за генеричке колекције до појаве collections framework-а у Јava SE 1.2. java.util.Enumeration<E> boolean hasMoreElements() враћа true ако има још елемената који нису посећени. E nextElement() враћа наредни елемент. Не звати га ако је hasMoreElements()вратио false. java.util.Hashtable<K, V> Enumeration<K> keys() враћа објекат енумерацију који обилази кључеве хеш-табеле. Enumeration<V> elements() враћа објекат енумерацију који обилази елементе хеш-табеле. java.util.Vector<E> Enumeration<E> elements() враћа објекат енумерацију који обилази елементе вектора.

Page 53: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

53

Property Maps Property map је мапа веома специјалног типа. Има три посебне карактеристике: - кључеви и вредности су стрингови - табела може бити сачувана у фајл и учитана из њега - друга табела се користи за подразумеване вредности. Класа која имплементира property map зове се Properties. Property мапе се обично користе за задавање конфигурационих опција за програме (поглавље 10). java.util.Properties Properties() креира празну property map. Properties(Properties defaults) креира празну property map са задатим подразумеваним вредностима. String getProperty(String key) враћа property, тј. стринг придружен кључу или стринг придружен кључу у табели подразумеваних вредности ако кључ није присутан у мапи. String getProperty(String key, String defaultValue) враћа property са подразумеваном вредношћу ако не пронађе кључ; враћа стринг придружен кључу или подразумевани стринг ако кључ није присутан у мапи. void load(InputStream in) учитава property map из InputStream. void store(OutputStream out, String commentString) смешта property map у OutputStream. Stacks Од верзије 1.0 стандардна библиотека има класу Stack са познатим методима push() и pop(). Међутим, класа Stack изведена је из класе Vector, што није задовољавајуће гледано из теоретске перспективе - могуће је применити не-стековске операције попут insert() и remove() и тако додати или уклонити вредност са произвољне позиције, а не само са врха стека. java.util.Stack<E> E push(E item) ставља item на стек и враћа item. E pop() скида и враћа елемент са врха стека. Не позивати овај метод ако је стек празан. E peek() враћа елемент са врха стека, не скидајући га са стека. Не позивати овај метод ако је стек празан.

Page 54: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

54

BitSets Објекат класе BitSet чува секвенцу битова. (То није скуп у математичком смислу - прикладнији појмови били би бит-вектор или бит-низ.) Користите га ако треба ефикасно да чувате секвенцу битова (нпр. флегова). Како он пакује битове у бајтове, далеко је ефикасније користити њега него ArrayList објеката типа Boolean. Класа BitSet нуди погодан интерфејс за читање, постављање и ресетовање појединачних битова. Употребом овог интерфејса избегава се „маскирање“ које би било неопходно када би се битови чували у променљивама типа int или long. Нпр. за објекат bucketOfBits класе BitSet

bucketOfBits.get(i) враћа true ако је i-ти бит укључен, а false иначе. Слично,

bucketOfBits.set(i) укључује i-ти бит. Коначно,

bucketOfBits.clear(i) искључује i-ти бит. java.util.BitSet BitSet(int initialCapacity) конструише bit set. int length() враћа „логичку дужину“: 1 плус индекс бита највише тежине који је постављен. boolean get(int bit) очитава бит. void set(int bit) поставља бит. void clear(int bit) чисти бит. void and(BitSet set) врши логичко „И“ текућег и задатог bit set-a. void or(BitSet set) врши логичко „ИЛИ“ текућег и задатог bit set-a. void xor(BitSet set) врши логичко „ЕКСКЛУЗИВНО ИЛИ“ текућег и задатог bit set-a.

Page 55: КОЛЕКЦИЈЕ - University of Belgradepoincare.matf.bg.ac.rs/~marija/oop/vezbe/cas10/Core Java... · 2013. 1. 13. · 1 Поглавље 13 у књизи: „Core Java - Volume

55

void andNot(BitSet set) чисти све битове текућег bit set-a који су укључени у задатом bit set-у. The “Sieve of Eratostheness” Benchmark (Ератостеново сито) benchmark - проблем за тестирање перформанси Као пример коришћења bit set-ова приказана је имплементација Ератостеновог сита, алгоритма за проналажење простих бројева. (Број је прост ако је дељив само самим собом и бројем 1, а Ератостеново сито је било један од првих откривених алгоритама за њихову енумерацију.) Алгоритам није нарочито добар, али је из неког разлога постао популаран benchmark за перформансе компајлера (није добар ни као benchmark јер углавном тестира битске операције). ПРИМЕР 7 (EratostenovoSito): Програм пребројава просте бројеве између 2 и 2,000,000. Има их 148,933 па вероватно не желите све да их штампате. Без залажења у превише детаља, кључно је проћи кроз bit set са 2 милиона битова. Најпре се сви битови укључе. Након тога искључују се битови који представљају умношке бројева за које је познато да су прости. Позиције битова који остану укључени након овог процеса представљају просте бројеве. ПРИМЕДБА: Иако Ератостеново сито није добар benchmark, аутори нису могли да одоле, па су упоредили време извршавања Јава и C++ имплементације алгоритма. Тестирање је извршено на 1.66-GHz dual core ThinkPad са 2GB RAM-a под оперативним системом Ubuntu 7.04. C++ (g++ 4.1.2): 360 ms Java (Java SE 6): 105 ms Ако се подигне ниво оптимизације C++ компајлера, он побеђује Јаву са временом од 60 ms. Јава то може достићи једино ако се програм извршава довољно дуго да се покрене Hotspot just-in-time компајлер.