Skip to content

Wykład 2 - Kolekcje i Stream API

  • Kolekcje przechowują dane; w odróżnieniu od tablic nie mają stałego rozmiaru (rosną/maleją dynamicznie).
  • Kolekcje nie przechowują typów prymitywnych - tylko typy referencyjne (parametryzacja generykami, np. <Integer>).
  • Najczęstsze klasy: ArrayList, Vector, LinkedList, HashSet, HashMap.
Cecha ArrayList Vector LinkedList HashSet HashMap
Struktura dynamiczna tablica dynamiczna tablica lista dwukierunkowa tablica haszująca tablica haszująca
Kolejność wg dodania wg dodania wg dodania brak gwarancji brak gwarancji
Duplikaty tak tak tak nie klucze: nie, wartości: tak
null tak tak tak 1× null 1 klucz null, wiele wart. null
Dostęp do elem. szybki szybki wolniejszy b. szybki b. szybki
Wstawianie w środku wolne wolne szybkie (znany węzeł) szybkie szybkie
Synchronizacja nie tak nie nie nie (HashTable: tak)
Typ Lista Lista Lista/Kolejka/Deque Zbiór (Set) Mapa (Map)

Fakty do zapamiętania:

  • ArrayList, Vector, LinkedList implementują interfejs List; w użyciu identyczne, różnią się implementacją.
  • ArrayList vs Vector: Vector jest synchronizowany (1 wątek), ArrayList nie (wiele wątków). Przy rozszerzaniu: Vector podwaja pamięć, ArrayList +50%.
  • LinkedList - szybsze dodawanie/usuwanie, wolniejszy dostęp (vs ArrayList/Vector odwrotnie).
  • HashSet - szybkie sprawdzanie istnienia elementu; HashMap - szybkie wyszukiwanie wartości po kluczu.

Trzy sposoby: Iterator + while(hasNext()), pętla for z iteratorem, pętla for-each (for (int x : collection)).

  • Dodane w Javie 8. Nie mylić ze strumieniami z pakietów Java IO.
  • Stream “opakowuje” źródło danych i przetwarza je funkcyjnie; kod deklaratywny (co, nie jak).
  • Umożliwia: filtrowanie, transformację, agregację oraz łatwą paralelizację.
  • Sekwencja elementów przetwarzanych funkcyjnie.
  • Źródła: kolekcje, tablice, pliki, generatory.
  • Nie przechowuje danych - operuje na źródle; nie modyfikuje źródła.
  • Strumień jest jednorazowy - by użyć ponownie, trzeba go utworzyć od nowa.

źródło → operacje pośrednie (transformacje) → operacja końcowa (wynik)

list.stream()
.filter(x -> x.length() > 3) // pośrednia
.map(String::toUpperCase) // pośrednia
.sorted() // pośrednia
.collect(Collectors.toList()); // końcowa

Tworzenie strumienia: list.stream(), Arrays.stream(arr), Stream.of("a","b"), Stream.generate(Math::random) (nieskończony).

Operacje pośrednie (zwracają nowy strumień)

Section titled “Operacje pośrednie (zwracają nowy strumień)”
Operacja Działanie Stateless/Stateful
filter(Predicate) wybiera elementy spełniające warunek stateless
map(Function) przekształca każdy element (często zmiana typu) stateless
flatMap spłaszcza strumień strumieni (np. List<List<T>>Stream<T>) stateless
sorted() sortuje (natural ordering lub Comparator) stateful
distinct() usuwa duplikaty (porównuje przez equals()) stateful
limit(n) pierwsze n elementów stateful
skip(n) pomija pierwsze n (paginacja z limit) stateful
Operacja Zwraca Działanie
collect(Collector) kolekcja/struktura zbiera wynik (toList, toSet, toMap, groupingBy…)
forEach(Consumer) nic akcja dla każdego elementu
count() long liczba elementów
reduce(start, acc) wartość redukcja do jednej wartości (suma, max…)
findFirst() Optional<T> pierwszy element
anyMatch(Predicate) boolean czy choć jeden spełnia (short-circuit)
allMatch(Predicate) boolean czy wszystkie spełniają (short-circuit)

Kolektory specjalne:

  • Collectors.groupingBy(klasyfikator)Map<K, List<T>> (grupowanie wg klucza).
  • Collectors.partitioningBy(predykat)Map<Boolean, List<T>> (podział na true/false).
  • Stateless - element przetwarzany niezależnie, brak stanu (np. map, filter). Bezpieczne dla parallelStream.
  • Stateful - zależy od innych elementów / przechowuje stan (np. sorted, distinct, limit). Kosztowne równolegle.
  • Równoległe przetwarzanie wielu wątkami, wykorzystuje ForkJoinPool (dzieli dane na zadania).
  • Najlepiej dla operacji stateless i niezależnych od kolejności na dużych zbiorach.
  • Nie używać dla: małych kolekcji, operacji zależnych od kolejności, efektów ubocznych/blokujących.
  • Gdy ważna kolejność → forEachOrdered() zamiast forEach().
  • Unikać efektów ubocznych, preferować dane niemutowalne, krótkie czytelne pipeline, nie nadużywać parallelStream.
  • lazy evaluation (leniwa ewaluacja) poprawia wydajność.

Do zapamiętania: pipeline = źródło + operacje pośrednie + końcowa; strumień jednorazowy, nie modyfikuje źródła; znać które operacje są stateful (sorted/distinct/limit) i które pośrednie vs końcowe.