→ Slide 1

Kolekcje i algorytmy. Strumienie. Operacje IO

→ Slide 2
  • Zwięzły sposób reprezentacji anonimowych funkcji (Java 8+).
  • Składnia: (parametry) → wyrażenie
    lub (parametry) → { instrukcje; }
  • Brak nazwy, mogą być przekazywane jako argumenty lub przypisywane do zmiennych.
  • Umożliwiają programowanie funkcyjne w Javie.
// Bez parametrów
Runnable r = () -> System.out.println("Hello!");
 
// Jeden parametr (nawiasy opcjonalne)
Consumer<String> print = s -> System.out.println(s);
 
// Wiele parametrów
Comparator<Integer> cmp = (a, b) -> a - b;
 
// Blok instrukcji
BinaryOperator<Integer> add = (a, b) -> {
    int result = a + b;
    return result;
};
→ Slide 3
  • Interfejs funkcyjny - interfejs zawierający dokładnie jedną metodę abstrakcyjną (Single Abstract Method - SAM).
  • Może zawierać dowolną liczbę metod default i static.
  • Adnotacja @FunctionalInterface - opcjonalna, ale zalecana (weryfikacja kompilatora).
  • Używany jako typ docelowy dla wyrażeń lambda i referencji do metod.
  • Przykłady: Runnable, Callable, Comparator, Comparable, Function, Predicate.
@FunctionalInterface
interface Transformer<T> {
    T transform(T input);
    // można dodawać metody default i static
    default T transformTwice(T input) {
        return transform(transform(input));
    }
}
 
Transformer<String> upper = s -> s.toUpperCase();
System.out.println(upper.transform("hello")); // HELLO
→ Slide 4

Przykład klasy anonimowej vs lambda:

// Klasa anonimowa
Comparator<String> comp1 = new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
};
 
// Równoważne wyrażenie lambda
Comparator<String> comp2 = (a, b) -> a.length() - b.length();
 
List<String> names = Arrays.asList("Ania", "Bartosz", "Ola");
names.sort(comp2);
System.out.println(names); // [Ola, Ania, Bartosz]

Uwaga: zmienne używane w klasach anonimowych i lambdach muszą być final lub efektywnie finalne (Java 8+).

→ Slide 5
  • Krótszy zapis lambdy wywołującej istniejącą metodę.
  • Składnia: Klasa::metoda
    lub obiekt::metoda
  • Cztery rodzaje referencji:
Rodzaj Składnia Odpowiednik lambdy
Metoda statyczna Klasa::metoda (x) → Klasa.metoda(x)
Metoda instancji (dowolny obiekt) Klasa::metoda (x) → x.metoda()
Metoda instancji (konkretny obiekt) obj::metoda (x) → obj.metoda(x)
Konstruktor Klasa::new (x) → new Klasa(x)
→ Slide 6
List<String> names = Arrays.asList("Ania", "Bartosz", "Ola");
 
// Referencja do metody statycznej
names.forEach(System.out::println);
 
// Referencja do metody instancji
names.stream()
     .map(String::toUpperCase)
     .forEach(System.out::println);
 
// Referencja do konstruktora
List<Integer> sizes = names.stream()
    .map(String::length)
    .collect(Collectors.toList());
→ Slide 7

Pakiet java.util.function - gotowe interfejsy funkcyjne:

Interfejs Metoda Opis
Function<T,R> R apply(T t) Przekształca T w R
Predicate<T> boolean test(T t) Sprawdza warunek
Consumer<T> void accept(T t) Konsumuje wartość
Supplier<T> T get() Dostarcza wartość
BiFunction<T,U,R> R apply(T t, U u) Dwa argumenty
UnaryOperator<T> T apply(T t) Funkcja tożsama
BinaryOperator<T> T apply(T t1, T t2) Dwa arg. tego samego typu
→ Slide 8
Function<String, Integer> len = String::length;
Predicate<Integer> isEven = n -> n % 2 == 0;
Consumer<String> log = s -> System.out.println("[LOG] " + s);
Supplier<List<String>> listFactory = ArrayList::new;
 
Predicate<Integer> isPositive = n -> n > 0;
Predicate<Integer> isPositiveEven = isEven.and(isPositive);
System.out.println(isPositiveEven.test(4));  // true
System.out.println(isPositiveEven.test(-2)); // false
→ Slide 9
  • Kolekcja - obiekt przechowujący grupę elementów.
  • Alternatywa dla tablic: dynamiczny rozmiar, bogate API.
  • Java Collections Framework (JCF) - wprowadzony w Java 2 (1998).
  • Zawiera: interfejsy, implementacje, algorytmy (Collections, Arrays).
  • Starsze klasy (przed JCF): Vector, Hashtable, Stack - zsynchronizowane, mniej elastyczne.
// Tablica - stały rozmiar
String[] arr = new String[3];
arr[0] = "Ania";
 
// Kolekcja - dynamiczny rozmiar
List<String> list = new ArrayList<>();
list.add("Ania");
list.add("Bartosz");
list.remove("Ania");
System.out.println(list.size()); // 1
→ Slide 10

Główne interfejsy (java.util):

Iterable
  └── Collection
        ├── List       (uporządkowane, duplikaty OK)
        │     ├── ArrayList
        │     └── LinkedList
        ├── Set        (bez duplikatów)
        │     ├── HashSet
        │     ├── LinkedHashSet
        │     └── SortedSet → TreeSet
        └── Queue      (kolejka FIFO/LIFO)
              ├── LinkedList
              └── PriorityQueue
Map (osobna hierarchia, nie extends Collection)
  ├── HashMap
  ├── LinkedHashMap
  └── SortedMap → TreeMap
→ Slide 11
Interfejs Charakterystyka
List<E> Elementy w kolejności, dostęp przez indeks, duplikaty OK
Set<E> Brak duplikatów, kolejność zależy od implementacji
SortedSet<E> Set posortowany naturalnie lub przez Comparator
Queue<E> Operacje FIFO: offer, poll, peek
Deque<E> Kolejka dwustronna: stos + kolejka
Map<K,V> Pary klucz-wartość, klucze unikalne
SortedMap<K,V> Map z posortowanymi kluczami
List<String> list = new ArrayList<>();     // indeks, duplikaty
Set<String> set = new HashSet<>();         // bez duplikatów
Queue<String> queue = new LinkedList<>();  // FIFO
Deque<String> stack = new ArrayDeque<>();  // stos (LIFO)
Map<String, Integer> map = new HashMap<>();// klucz -> wartość
→ Slide 12
  • Pętla for (T x : kolekcja) działa dla obiektów implementujących interfejs Iterable<T>.
  • Interfejs Collection dziedziczy po Iterable, dlatego wszystkie listy, sety i kolejki obsługują ten zapis.
  • Kompilator zamienia pętlę for-each na użycie iteratora.
  • Dla mapy iterujemy zwykle po entrySet(), keySet() albo values().
List<String> names = List.of("Ania", "Ola", "Bartosz");
 
for (String name : names) {
    System.out.println(name);
}
 
Map<String, Integer> scores = Map.of("Ania", 90, "Ola", 88);
for (Map.Entry<String, Integer> e : scores.entrySet()) {
    System.out.println(e.getKey() + " -> " + e.getValue());
}
→ Slide 13
  • Iterator<T> umożliwia ręczne przechodzenie po elementach (hasNext(), next()) i bezpieczne usuwanie (remove()).
  • Spliterator<T> (Java 8+) wspiera dzielenie danych na części, głównie pod strumienie równoległe.
List<Integer> nums = new ArrayList<>(List.of(1, 2, 3, 4, 5));
 
Iterator<Integer> it = nums.iterator();
while (it.hasNext()) {
    Integer n = it.next();
    if (n % 2 == 0) it.remove();
}
System.out.println(nums); // [1, 3, 5]
 
Spliterator<Integer> sp = nums.spliterator();
Spliterator<Integer> sp2 = sp.trySplit();
if (sp2 != null) sp2.forEachRemaining(System.out::println);
sp.forEachRemaining(System.out::println);
→ Slide 14
Klasa Bazuje na Właściwości
ArrayList<E> tablica dynamiczna szybki dostęp O(1), wolny insert w środku
LinkedList<E> lista dwukierunkowa szybki insert O(1), wolny dostęp O(n)
HashSet<E> tablica haszująca O(1) add/contains, brak porządku
LinkedHashSet<E> hash + lista O(1) operacje, zachowuje kolejność wstawiania
TreeSet<E> drzewo czerwono-czarne O(log n), posortowane
HashMap<K,V> tablica haszująca O(1) get/put, brak porządku kluczy
TreeMap<K,V> drzewo czerwono-czarne O(log n), posortowane klucze
ArrayDeque<E> tablica cykliczna szybszy stos/kolejka niż Stack/LinkedList
→ Slide 15

Podstawowe operacje interfejsu Collection:

List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
 
list.add("D");              // dodaj na koniec
list.add(1, "X");           // wstaw na pozycję 1
list.remove("B");           // usuń element
list.remove(0);             // usuń po indeksie
boolean has = list.contains("C");  // sprawdź obecność
int size = list.size();
 
// Iteracja
for (String s : list) System.out.println(s);
 
// Iterator (z możliwością usuwania)
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    if (it.next().equals("X")) it.remove();
}
 
// Operacje grupowe
list.addAll(Arrays.asList("E", "F"));
list.removeIf(s -> s.equals("A"));  // Java 8+
→ Slide 16
Map<String, Integer> scores = new HashMap<>();
 
scores.put("Ania", 90);
scores.put("Bartosz", 75);
scores.put("Ola", 88);
 
int score = scores.get("Ania");          // 90
scores.getOrDefault("Kasia", 0);         // 0 (brak klucza)
scores.putIfAbsent("Kasia", 60);         // dodaj jeśli brak
scores.replace("Bartosz", 80);           // zastąp wartość
 
// Iteracja
for (Map.Entry<String, Integer> e : scores.entrySet()) {
    System.out.println(e.getKey() + " -> " + e.getValue());
}
 
// Java 8+
scores.forEach((k, v) -> System.out.println(k + ": " + v));
scores.compute("Ania", (k, v) -> v + 10); // Ania = 100
→ Slide 17

Narzędziowa klasa java.util.Collections - metody statyczne:

List<Integer> nums = new ArrayList<>(Arrays.asList(3, 1, 4, 1, 5, 9));
 
Collections.sort(nums);                    // sortowanie rosnące
Collections.sort(nums, Comparator.reverseOrder()); // malejące
Collections.shuffle(nums);                 // losowe tasowanie
Collections.reverse(nums);                 // odwrócenie
Collections.swap(nums, 0, 1);             // zamiana elementów
 
int max = Collections.max(nums);           // maksimum
int min = Collections.min(nums);           // minimum
int freq = Collections.frequency(nums, 1); // liczba wystąpień
 
// Synchronizacja (thread-safe wrapper)
List<Integer> syncList = Collections.synchronizedList(nums);
// Niemodyfikowalna kopia
List<Integer> readOnly = Collections.unmodifiableList(nums);
→ Slide 18
  • Stream<T> - sekwencja elementów wspierająca operacje agregujące (Java 8+).
  • NIE przechowuje danych - przetwarza dane ze źródła (kolekcja, tablica, plik…).
  • Operacje pośrednie (lazy) - przekształcają strumień, zwracają nowy Stream.
  • Operacje końcowe (terminal) - konsumują strumień, zwracają wynik.
  • Strumień może być przetworzony tylko raz (nie jak Iterator - raz zużyty).
  • Obsługa strumieni równoległych (parallelStream()).
List<String> names = Arrays.asList("Ania", "Bartosz", "Ola", "Adam");
 
long count = names.stream()               // tworzenie strumienia
    .filter(n -> n.startsWith("A"))       // pośrednia: filtrowanie
    .map(String::toUpperCase)             // pośrednia: mapowanie
    .count();                             // końcowa: liczenie
 
System.out.println(count); // 2
→ Slide 19
// Ze kolekcji
List<String> list = List.of("a", "b", "c");
Stream<String> s1 = list.stream();
Stream<String> s2 = list.parallelStream();  // równoległy
 
// Z tablicy
Stream<String> s3 = Arrays.stream(new String[]{"x", "y"});
 
// Stream.of
Stream<Integer> s4 = Stream.of(1, 2, 3, 4, 5);
 
// Strumienie liczbowe (bez autoboxing)
IntStream ints = IntStream.range(1, 11);       // 1..10
LongStream longs = LongStream.rangeClosed(1, 5); // 1..5 włącznie
DoubleStream doubles = DoubleStream.of(1.5, 2.5);
 
// Nieskończone strumienie
Stream<Integer> evens = Stream.iterate(0, n -> n + 2);
Stream<Double> randoms = Stream.generate(Math::random);
→ Slide 20
  • filter(Predicate) - odfiltruj elementy
  • map(Function) - przekształć każdy element
  • flatMap(Function) - spłaszcz strumień strumieni
  • sorted() / sorted(Comparator) - posortuj
  • distinct() - usuń duplikaty
  • limit(n) - weź pierwsze n elementów
  • skip(n) - pomiń pierwsze n elementów
  • peek(Consumer) - podejrzyj element (debugowanie)
List<String> words = Arrays.asList("hello", "world", "java", "stream");
 
List<String> result = words.stream()
    .filter(w -> w.length() > 4)          // "hello", "world", "stream"
    .map(String::toUpperCase)             // "HELLO", "WORLD", "STREAM"
    .sorted()                             // "HELLO", "STREAM", "WORLD"
    .collect(Collectors.toList());
 
// flatMap - spłaszczanie
List<List<Integer>> nested = List.of(List.of(1, 2), List.of(3, 4));
List<Integer> flat = nested.stream()
    .flatMap(Collection::stream)
    .collect(Collectors.toList()); // [1, 2, 3, 4]
→ Slide 21
List<Integer> nums = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
 
// Zbieranie do kolekcji
List<Integer> evens = nums.stream()
    .filter(n -> n % 2 == 0)
    .collect(Collectors.toList());       // [2, 4, 6, 8, 10]
 
// Redukcja
int sum = nums.stream().reduce(0, Integer::sum); // 55
Optional<Integer> max = nums.stream().max(Integer::compareTo); // 10
 
// Statystyki
IntSummaryStatistics stats = nums.stream()
    .mapToInt(Integer::intValue)
    .summaryStatistics();
System.out.println(stats.getAverage()); // 5.5
 
// Sprawdzanie warunków
boolean anyEven = nums.stream().anyMatch(n -> n % 2 == 0); // true
boolean allPos  = nums.stream().allMatch(n -> n > 0);       // true
boolean noneNeg = nums.stream().noneMatch(n -> n < 0);      // true
 
// Znalezienie elementu
Optional<Integer> first = nums.stream().filter(n -> n > 5).findFirst();
→ Slide 22
  • java.io - klasyczny pakiet (strumienie bajtowe i znakowe).
  • java.nio / java.nio.file - nowoczesne API (Java 7+), nieblokujące I/O.
  • Klasa java.nio.file.Files - wygodne metody do operacji na plikach.
  • Hierarchia strumieni IO:
    • Strumienie bajtowe: InputStream / OutputStream (dane binarne)
    • Strumienie znakowe: Reader / Writer (tekst, obsługa kodowania)
  • Buforowane wersje: BufferedReader, BufferedWriter - lepsza wydajność.
// Ścieżki (java.nio.file.Path)
Path path = Path.of("data", "plik.txt");   // Java 11+
Path abs  = path.toAbsolutePath();
Path parent = path.getParent();            // "data"
String name = path.getFileName().toString(); // "plik.txt"
→ Slide 23
// Zapis do pliku (java.nio - zalecane)
Path path = Path.of("wyniki.txt");
List<String> lines = List.of("Linia 1", "Linia 2", "Linia 3");
Files.write(path, lines, StandardCharsets.UTF_8);
 
// Odczyt całego pliku
String content = Files.readString(path, StandardCharsets.UTF_8); // Java 11+
List<String> readLines = Files.readAllLines(path, StandardCharsets.UTF_8);
 
// Odczyt wiersz po wierszu (duże pliki)
try (BufferedReader reader = Files.newBufferedReader(path)) {
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
}
 
// Zapis za pomocą BufferedWriter
try (BufferedWriter writer = Files.newBufferedWriter(path,
        StandardOpenOption.APPEND)) {
    writer.write("Dodatkowa linia");
    writer.newLine();
}
→ Slide 24
  • Automatyczne zamknięcie zasobów po wyjściu z bloku try (Java 7+).
  • Zasób musi implementować interfejs AutoCloseable (metoda close()).
  • Zastępuje blok finally z ręcznym close().
  • Wyjątki z close() są „tłumione” (suppressed) jeśli blok też rzucił wyjątek.
// Stary sposób (przed Java 7)
BufferedReader reader = null;
try {
    reader = new BufferedReader(new FileReader("plik.txt"));
    System.out.println(reader.readLine());
} finally {
    if (reader != null) reader.close();
}
 
// Try-with-resources (Java 7+) - zalecane
try (BufferedReader reader = new BufferedReader(new FileReader("plik.txt"))) {
    System.out.println(reader.readLine());
} // reader.close() wywołane automatycznie
 
// Wiele zasobów
try (InputStream in  = new FileInputStream("wejście.bin");
     OutputStream out = new FileOutputStream("wyjście.bin")) {
    in.transferTo(out); // Java 9+
}
→ Slide 25

Ćwiczenie 1: Monte Carlo - przybliżanie liczby $\pi$

Zaimplementuj poniższe zadania, korzystając z kolekcji, strumieni i operacji IO.

Wyznacz wartość przybliżoną liczby $\pi$ metodą Monte Carlo: losuj punkty w kwadracie o boku 1, sprawdź ile z nich trafia do ćwiartki koła wpisanej w ten kwadrat, czyli takich, które spełniają warunek $$x^2 + y^2 \leq 1$$ Przybliżona liczba $\pi \approx 4\frac{k}{n} $, gdzie $k$ to liczba punktów trafiających do ćwiartki koła, a $n$ to całkowita liczba losowanych punktów.

Wykonaj symulację dla 1 miliona losowych punktów i wypisz wynik.

W rozwiązaniu wykorzystaj kolekcje i strumienie. Porównaj czas wykonania algorytmu dla różnych implementacji:

  • z użyciem strumieni vs tradycyjna pętla for do zliczania punktów w kole
  • z użyciem ArrayList vs LinkedList do przechowywania punktów
  • zrównoleglony strumień (parallel()) vs sekwencyjny strumień do przetwarzania punktów.
  • zrównoleglony strumień z lokalnym generatorem liczb losowych (ThreadLocalRandom) vs globalny (Random).

Do pomiaru czasu wykonania użyj System.nanoTime() lub System.currentTimeMillis().

Ćwiczenie 2: Odczyt danych z pliku i przetwarzanie

Wczytaj plik CSV tips.csv i wyznacz średnią wartość rachunku (kolumna total_bill) dla każdej płci (kolumna sex). Użyj strumieni i kolekcji do grupowania danych.

Format pliku tips.csv:

"total_bill","tip","sex","smoker","day","time","size"
16.99,1.01,"Female","No","Sun","Dinner",2
10.34,1.66,"Male","No","Sun","Dinner",3
...
→ Slide 26

Napisz program generujący słownik wyrazów zawartych w pliku tekstowym. Program wczytuje nazwę pliku ze standardowego wejścia, a następnie wyświetla posortowaną alfabetycznie listę wyrazów wraz z liczbą ich wystąpień.

Przykładowe wyjście:

Podaj nazwę pliku: tekst.txt
ala      5
kota     2
ma       1

Wymagania:

  • wyraz to dowolny ciąg liter (znaków alfabetycznych Character.isLetter()). Pozostałe znaki są separatorami wyrazów. Wielkość liter jest nierozróżnialna, tj. abc i ABC traktujemy jako ten sam wyraz — program wypisuje wyrazy pisane małymi literami,
  • do przechowywania słownika użyj odpowiedniej kolekcji (np. TreeMap<Wyraz, Integer>)
  • porównywanie wyrazów zrealizuj za pomocą komparatora w postaci funkcji lambda, referencji do metody porównującej wyrazy
  • obsłuż wyjątki związane z odczytem pliku za pomocą schematu try-with-resources
  • opcjonalnie: wykorzystaj strumienie do przetwarzania danych i generowania wyników