~~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