(parametry) → wyrażenie (parametry) → { instrukcje; }// 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; };
default i static.@FunctionalInterface - opcjonalna, ale zalecana (weryfikacja kompilatora).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
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+).
Klasa::metoda obiekt::metoda| 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) |
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());
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 |
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
Collections, Arrays).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
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
| 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ść
for (T x : kolekcja) działa dla obiektów implementujących interfejs Iterable<T>.Collection dziedziczy po Iterable, dlatego wszystkie listy, sety i kolejki obsługują ten zapis.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()); }
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);
| 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 |
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+
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
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);
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
// 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);
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]
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();
java.nio.file.Files - wygodne metody do operacji na plikach.InputStream / OutputStream (dane binarne)Reader / Writer (tekst, obsługa kodowania)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"
// 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(); }
try (Java 7+).AutoCloseable (metoda close()).finally z ręcznym close().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+ }
Ć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:
ArrayList vs LinkedList do przechowywania punktówparallel()) vs sekwencyjny strumień do przetwarzania punktów.
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 ...
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:
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,TreeMap<Wyraz, Integer>)