~~NOCACHE~~
~~REVEAL theme=simple&disableLayout=0&transition=none&controls=1&show_progress_bar=1&build_all_lists=0&show_image_borders=0&horizontal_slide_level=2&enlarge_vertical_slide_headers=0&show_slide_details=1&open_in_new_window=1&size=1280x800~~
====== Kolekcje i algorytmy. Strumienie. Operacje IO ======
===== Wyrażenia lambda =====
* 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 print = s -> System.out.println(s);
// Wiele parametrów
Comparator cmp = (a, b) -> a - b;
// Blok instrukcji
BinaryOperator add = (a, b) -> {
int result = a + b;
return result;
};
===== Interfejsy funkcyjne =====
* **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 transform(T input);
// można dodawać metody default i static
default T transformTwice(T input) {
return transform(transform(input));
}
}
Transformer upper = s -> s.toUpperCase();
System.out.println(upper.transform("hello")); // HELLO
===== Klasy anonimowe i lambda =====
Przykład klasy anonimowej vs lambda:
// Klasa anonimowa
Comparator comp1 = new Comparator() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
};
// Równoważne wyrażenie lambda
Comparator comp2 = (a, b) -> a.length() - b.length();
List 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+).
===== Referencje do metod =====
* 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)'' |
===== Przykład =====
List 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 sizes = names.stream()
.map(String::length)
.collect(Collectors.toList());
===== Predefiniowane interfejsy funkcyjne =====
Pakiet ''java.util.function'' - gotowe interfejsy funkcyjne:
^ Interfejs ^ Metoda ^ Opis ^
| ''Function'' | ''R apply(T t)'' | Przekształca T w R |
| ''Predicate'' | ''boolean test(T t)'' | Sprawdza warunek |
| ''Consumer'' | ''void accept(T t)'' | Konsumuje wartość |
| ''Supplier'' | ''T get()'' | Dostarcza wartość |
| ''BiFunction'' | ''R apply(T t, U u)'' | Dwa argumenty |
| ''UnaryOperator'' | ''T apply(T t)'' | Funkcja tożsama |
| ''BinaryOperator'' | ''T apply(T t1, T t2)'' | Dwa arg. tego samego typu |
===== Przykład =====
Function len = String::length;
Predicate isEven = n -> n % 2 == 0;
Consumer log = s -> System.out.println("[LOG] " + s);
Supplier> listFactory = ArrayList::new;
Predicate isPositive = n -> n > 0;
Predicate isPositiveEven = isEven.and(isPositive);
System.out.println(isPositiveEven.test(4)); // true
System.out.println(isPositiveEven.test(-2)); // false
===== Wprowadzenie do kolekcji =====
* **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 list = new ArrayList<>();
list.add("Ania");
list.add("Bartosz");
list.remove("Ania");
System.out.println(list.size()); // 1
===== Hierarchia kolekcji w Javie =====
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
===== Interfejsy kolekcji =====
^ Interfejs ^ Charakterystyka ^
| ''List'' | Elementy w kolejności, dostęp przez indeks, duplikaty OK |
| ''Set'' | Brak duplikatów, kolejność zależy od implementacji |
| ''SortedSet'' | Set posortowany naturalnie lub przez Comparator |
| ''Queue'' | Operacje FIFO: ''offer'', ''poll'', ''peek'' |
| ''Deque'' | Kolejka dwustronna: stos + kolejka |
| ''Map'' | Pary klucz-wartość, klucze unikalne |
| ''SortedMap'' | Map z posortowanymi kluczami |
List list = new ArrayList<>(); // indeks, duplikaty
Set set = new HashSet<>(); // bez duplikatów
Queue queue = new LinkedList<>(); // FIFO
Deque stack = new ArrayDeque<>(); // stos (LIFO)
Map map = new HashMap<>();// klucz -> wartość
===== Iterable i pętla for-each =====
* Pętla ''for (T x : kolekcja)'' działa dla obiektów implementujących interfejs ''Iterable''.
* 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 names = List.of("Ania", "Ola", "Bartosz");
for (String name : names) {
System.out.println(name);
}
Map scores = Map.of("Ania", 90, "Ola", 88);
for (Map.Entry e : scores.entrySet()) {
System.out.println(e.getKey() + " -> " + e.getValue());
}
===== Iterator i Spliterator =====
* ''Iterator'' umożliwia ręczne przechodzenie po elementach (''hasNext()'', ''next()'') i bezpieczne usuwanie (''remove()'').
* ''Spliterator'' (Java 8+) wspiera dzielenie danych na części, głównie pod strumienie równoległe.
List nums = new ArrayList<>(List.of(1, 2, 3, 4, 5));
Iterator it = nums.iterator();
while (it.hasNext()) {
Integer n = it.next();
if (n % 2 == 0) it.remove();
}
System.out.println(nums); // [1, 3, 5]
Spliterator sp = nums.spliterator();
Spliterator sp2 = sp.trySplit();
if (sp2 != null) sp2.forEachRemaining(System.out::println);
sp.forEachRemaining(System.out::println);
===== Implementacje kolekcji =====
^ Klasa ^ Bazuje na ^ Właściwości ^
| ''ArrayList'' | tablica dynamiczna | szybki dostęp O(1), wolny insert w środku |
| ''LinkedList'' | lista dwukierunkowa | szybki insert O(1), wolny dostęp O(n) |
| ''HashSet'' | tablica haszująca | O(1) add/contains, brak porządku |
| ''LinkedHashSet'' | hash + lista | O(1) operacje, zachowuje kolejność wstawiania |
| ''TreeSet'' | drzewo czerwono-czarne | O(log n), posortowane |
| ''HashMap'' | tablica haszująca | O(1) get/put, brak porządku kluczy |
| ''TreeMap'' | drzewo czerwono-czarne | O(log n), posortowane klucze |
| ''ArrayDeque'' | tablica cykliczna | szybszy stos/kolejka niż ''Stack''/''LinkedList'' |
===== Operacje na kolekcjach =====
Podstawowe operacje interfejsu ''Collection'':
List 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 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+
===== Operacje na mapach =====
Map 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 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
===== Klasa Collections - algorytmy =====
Narzędziowa klasa ''java.util.Collections'' - metody statyczne:
List 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 syncList = Collections.synchronizedList(nums);
// Niemodyfikowalna kopia
List readOnly = Collections.unmodifiableList(nums);
===== Strumienie - wprowadzenie =====
* **Stream** - 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 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
===== Tworzenie strumieni =====
// Ze kolekcji
List list = List.of("a", "b", "c");
Stream s1 = list.stream();
Stream s2 = list.parallelStream(); // równoległy
// Z tablicy
Stream s3 = Arrays.stream(new String[]{"x", "y"});
// Stream.of
Stream 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 evens = Stream.iterate(0, n -> n + 2);
Stream randoms = Stream.generate(Math::random);
===== Operacje pośrednie na strumieniach =====
* **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 words = Arrays.asList("hello", "world", "java", "stream");
List 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> nested = List.of(List.of(1, 2), List.of(3, 4));
List flat = nested.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList()); // [1, 2, 3, 4]
===== Operacje końcowe na strumieniach =====
List nums = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Zbieranie do kolekcji
List 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 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 first = nums.stream().filter(n -> n > 5).findFirst();
===== Operacje IO - wprowadzenie =====
* **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"
===== Odczyt i zapis plików =====
// Zapis do pliku (java.nio - zalecane)
Path path = Path.of("wyniki.txt");
List 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 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();
}
===== Try-with-resources =====
* 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+
}
===== Ćwiczenia praktyczne =====
** Ć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.
{{ zajecia:mn1_2021_2:mc_pi.png?500 |}}
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 ({{https://docs.oracle.com/javase/8/docs//api/java/util/concurrent/ThreadLocalRandom.html|ThreadLocalRandom}}) vs globalny ({{https://docs.oracle.com/javase/8/docs//api/java/util/Random.html|Random}}).
Do pomiaru czasu wykonania użyj ''System.nanoTime()'' lub ''System.currentTimeMillis()''.
===== =====
**Ćwiczenie 2: Odczyt danych z pliku i przetwarzanie**
Wczytaj plik CSV {{zajecia:java_2026_1: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
...
===== Zadanie 4. Słownik wyrazów =====
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'')
* 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