Seznam, množina, iterátory
Potomci Collection
Podívejme se na potomky třídy Collection, konkrétně:
• rozhraní List, implementace:
◦ ArrayList
◦ LinkedList
• rozhraní Set, implementace:
◦ HashSet
Seznam, List
• něco jako dynamické pole
• každý uložený prvek má svou pozici — číselný index
• index je celočíselný, nezáporný, typu int
• možnost procházení seznamu dopředně i zpětně
• lze pracovat i s podseznamy:
List subList(int fromIndex, int toIndex)
Implementace seznamu — ArrayList
• nejpoužívanější implementace seznamu
• využívá pole pro uchování prvků
• při zvětšování/zmenšování se vytváří nové pole a prvky se musejí přesouvat (gif)
• rychlý přístup k prvkům dle indexu
• pomalé operace přidávání a odebírání prvků blíže k začátku seznamu (pole, v němž je seznam,
se musí realokovat)
Javadoc třídy ArrayList
1
Implementace seznamu — LinkedList
• druhá nejpoužívanější implementace seznamu
• využívá zřetězený seznam pro uchování prvků
• pomalejší operace přístupu k prvkům dle indexu "uvnitř" seznamu
• rychlejší operace přidávání a odebírání prvků na začátku a na konci, resp. blízko nich
Javadoc třídy LinkedList
ArrayList vs. LinkedList
Kontejnery ukladají pouze jeden typ, v tomto případě String.
Výkonnostní porovnání seznamů
• Operace nad ArrayList vs LinkedList
add 100000 elements | 12 ms | 8 ms
remove all elements from last to first | 5 ms | 9 ms
add 100000 elements at 0th position | **1025 ms** | 18 ms
remove all elements from 0th position | **1014 ms** | 10 ms
add 100000 elements at random position | 483 ms | **34504 ms**
remove all elements from random position | 462 ms | **36867 ms**
2
Konstruktory seznamů
• ArrayList()
◦ vytvoří prázdný seznam (s kapacitou 10 prvků)
• ArrayList(int initialCapacity)
◦ vytvoří prázdný seznam s danou kapacitou
• ArrayList(Collection extends E> c)
◦ vytvoří seznam a naplní ho prvky kolekce c
Kapacita reprezentuje interní kapacitu, neznamená to počet null prvků v nové
kolekci!
Vytváření kolekcí
Statické "factory" metody:
• List.of(elem1, elem2, …)
◦ vytvoří seznam a naplní ho danými prvky
◦ vrátí nemodifikovatelnou kolekci
• analogicky Set.of, Map.of
• jestli chceme kolekci modifikovatelnou, musíme vytvořit novou:
List modifiableList = new ArrayList<>(List.of("Y", "N"));
Na zamyšlení
• Jak udělám se seznamu typu List kolekci Collection?
// change the type, it is its superclass
Collection collection = list;
• Jak udělám z kolekce Collection seznam List ?
// create new list
List l = new ArrayList<>(collection);
Metody rozhraní List I
Rozhraní List dědí od Collection.
3
Kromě metod v Collection obsahuje další metody:
• E get(int index)
◦ vrátí prvek na daném indexu
◦ IndexOutOfBoundsException je-li mimo rozsah
• E set(int index, E element)
◦ nahradí prvek s indexem index prvkem element
◦ vrátí předešlý prvek
Metody rozhraní List II
• void add(int index, E element)
◦ přidá prvek na daný index (prvky za ním posune)
• E remove(int index)
◦ odstraní prvek na daném indexu (prvky za ním posune)
◦ vrátí odstraněný prvek
• int indexOf(Object o)
◦ vrátí index prvního výskytu o
◦ jestli kolekce prvek neobsahuje, vrátí -1
• int lastIndexOf(Object o) pro index posledního výskytu
Příklad použití seznamu
List list = new ArrayList<>();
list.add("A");
list.add("C");
list.add(1, "B");
// ["A", "B", "C"]
list.get(2); // "C"
list.set(1, "D"); // "B"
list.indexOf("D"); // 1
Množina, Set
• odpovídá matematické představě množiny
• prvek lze do množiny vložit nejvýš jedenkrát
• při porovnávání rozhoduje rovnost podle výsledku volání equals
• umožňuje rychlé dotazování na přítomnost prvku
4
• provádí rychle atomické operace (se složitostí O(1), O(log(n))):
◦ vkládání prvku — add
◦ odebírání prvku — remove
◦ dotaz na přítomnost prvku — contains
Množiny jsou primárně bez pořadí, bez uspořádání, existuje však i množina s
uspořádáním.
Equals & hashCode — opakování
Equals
zjistí, jestli jsou objekty logicky stejné (porovnání atributů).
HashCode
vrací pro logicky stejné objekty stejné číslo, haš.
Hash
je falešné ID — pro různé objekty může hashCode vracet stejný haš.
Implementace množiny — HashSet
• Ukladá objekty do hašovací tabulky podle haše
• Ideálně konstantní operace (tj. sub-logaritmická složitost)
• Když má více prvků stejný haš, existuje více způsobů řešení
• Pro (ne úplně ideální) hashCode x + y vypadá tabulka následovně:
haš | objekt
0 | [0,0]
1 | [1,0]
2 |
3 | [2,1]
Javadoc třídy HashSet
HashSet pod lupou
• boolean contains(Object o)
◦ vypočte haš tázaného prvku o
◦ v tabulce najde objekt uložený pod stejným hašem
◦ objekt porovná s o pomocí equals
5
• Co když mají všechny objekty stejný haš?
◦ Množinové operace budou velmi, velmi pomalé.
• Co když porušíme kontrakt metody hashCode (pro stejné objekty vrátí různá čísla)?
◦ Množina přestane fungovat jako množina, bude obsahovat duplicity!
Další implmentací množiny je LinkedHashSet = Hash Table + Linked List
Další lineární struktury
zásobník
třída Stack, struktura LIFO
fronta
třída Queue, struktura FIFO
• fronta může být také prioritní — PriorityQueue
oboustranná fronta
třída Deque (čteme "deck")
• slučuje vlastnosti zásobníku a fronty
• nabízí operace příslušné oběma typům
Starší typy kontejnerů
• Existují tyto starší typy kontejnerů (za → uvádíme náhradu):
◦ Hashtable → HashMap, HashSet (podle účelu)
◦ Vector → List
◦ Stack → List nebo lépe Queue či Deque
Kontejnery a primitivní typy
• Kontejnery ukládájí pouze odkazy na objekty, neukládají primitivní typy.
• Proto používame jejich objektové protějšky — Integer, Char, Boolean, Double…
• Java automaticky dělá tzv. autoboxing — konverzi primitivního typu na objekt
• Pro zpětnou konverzi se analogicky dělá tzv. unboxing
List list = new ArrayList<>();
list.add(new Integer(1));
list.add(1); // autoboxing
int primitiveType = list.get(0); // unboxing
6
Procházení kolekcí
Základní typy:
for-each cyklus
• jednoduché, intuitivní
• nepoužitelné pro modifikace samotné kolekce
iterátory
• náročnější, užitečnější
• modifikace povolena
lambda výrazy s forEach
• strašidelné
For-each cyklus I
• Je rozšířenou syntaxí cyklu for.
• Umožňuje procházení kolekcí i polí.
List numbers = List.of(1, 1, 2, 3, 5);
for(Integer i: list) {
System.out.println(i);
}
For-each cyklus II
• For-each neumožňuje modifikace kolekce.
• Jestli kolekci změníme, nemůžeme pokračovat v iterování — dojde k vyhození
ConcurrentModificationException.
• Odstranění prvku a vyskočení z cyklu však funguje:
Set set = Set.of("Donald Trump", "Barrack Obama");
for(String s: set) {
if (s.equals("Donald Trump")) {
set.remove(s);
break;
}
}
7
Iterátory
• Sekvenční procházení prvků kolekce v neurčeném pořadí nebo uspořádání (u uspořádaných
kolekcí)
• Každý iterátor musí implementovat velmi jednoduché rozhraní Iterator
• Běžné použití pomocí while:
Set set = Set.of(1, 2, 3);
Iterator iterator = set.iterator();
while(iterator.hasNext()) {
Integer element = iterator.next();
...
}
Metody iterátorů
• E next()
◦ vrátí následující prvek
◦ NoSuchElementException jestli iterace nemá žádné zbývající prvky
• boolean hasNext()
◦ true jestli iterace obsahuje nějaký prvek
• void remove()
◦ odstraní prvek z kolekce
◦ maximálně jednou mezi jednotlivými voláními next()
Iterátor — příklad
Pro procházení iterátoru se dá použít i for cyklus:
Set set = Set.of("Donald Trump", "Barrack Obama", "Hillary Clinton");
for (Iterator iter = set.iterator(); iter.hasNext();) {
String element = iter.next();
if (!element.equals("Barrack Obama")) iter.remove();
}
Roli iterátoru plnil dříve výčet (Enumeration) — nepoužívat.
8
Repl.it demo k iterátorům (a komparátorům
z příští přednášky)
• https://repl.it/@tpitner/PB162-Java-Lecture-08-iterators-and-comparator
Kontejnery kontejnerů
• Kromě kontejnerů obsahujících již koncové hodnoty, jsou často používané i kontejnery
obsahující další kontejnery:
• Například List> bude obsahovat seznam množin celých čísel, tedy třeba [{1, 4,
-2}, {0}, {-1, 3}].
• Nebo mapa, která studentům přiřazuje seznam známek Map>.
Repl.it demo ke kontejnerům kontejnerů
• https://repl.it/@tpitner/PB162-Java-Lecture-08-map-of-lists
9