List<E> subList(int fromIndex, int toIndex)
Tomáš Pitner, Radek Ošlejšek, Marek Šabo
Collection
Podívejme se na potomky třídy Collection
, konkrétně:
rozhraní List
, implementace:
ArrayList
LinkedList
rozhraní Set
, implementace:
HashSet
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<E> subList(int fromIndex, int toIndex)
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)
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
Kontejnery ukladají pouze jeden typ, v tomto případě String . |
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**
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! |
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<String> modifiableList = new ArrayList<>(List.of("Y", "N"));
Jak udělám se seznamu typu List
kolekci Collection
?
// change the type, it is its superclass
Collection<Long> collection = list;
Jak udělám z kolekce Collection
seznam List
?
// create new list
List<Long> l = new ArrayList<>(collection);
List
IRozhraní List
dědí od Collection
.
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
List
IIvoid 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
List<String> 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
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
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. |
zjistí, jestli jsou objekty logicky stejné (porovnání atributů).
vrací pro logicky stejné objekty stejné číslo, haš.
je falešné ID — pro různé objekty může hashCode
vracet stejný haš.
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]
HashSet
pod lupouboolean 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
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 |
třída Stack
, struktura LIFO
třída Queue
, struktura FIFO
fronta může být také prioritní — PriorityQueue
třída Deque
(čteme "deck")
slučuje vlastnosti zásobníku a fronty
nabízí operace příslušné oběma typům
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 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<Integer> list = new ArrayList<>();
list.add(new Integer(1));
list.add(1); // autoboxing
int primitiveType = list.get(0); // unboxing
Základní typy:
jednoduché, intuitivní
nepoužitelné pro modifikace samotné kolekce
náročnější, užitečnější
modifikace povolena
forEach
strašidelné
Je rozšířenou syntaxí cyklu for
.
Umožňuje procházení kolekcí i polí.
List<Integer> numbers = List.of(1, 1, 2, 3, 5);
for(Integer i: list) {
System.out.println(i);
}
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<String> set = Set.of("Donald Trump", "Barrack Obama");
for(String s: set) {
if (s.equals("Donald Trump")) {
set.remove(s);
break;
}
}
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<E>
Běžné použití pomocí while
:
Set<Integer> set = Set.of(1, 2, 3);
Iterator<Integer> iterator = set.iterator();
while(iterator.hasNext()) {
Integer element = iterator.next();
...
}
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()
Pro procházení iterátoru se dá použít i for
cyklus:
Set<String> set = Set.of("Donald Trump", "Barrack Obama", "Hillary Clinton");
for (Iterator<String> 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. |
Kromě kontejnerů obsahujících již koncové hodnoty, jsou často používané i kontejnery obsahující další kontejnery:
Například List<Set<Integer>>
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<Student, List<Integer>>
.