Лямбда-выражения
Лямбда — краткая запись реализации функционального интерфейса, то есть интерфейса с одним абстрактным методом.
import java.util.ArrayList;import java.util.Collections;import java.util.List;public class LambdaBasics { public static void main(String[] args) { List<String> words = new ArrayList<>(List.of("слива", "яблоко", "киви")); Collections.sort(words, (a, b) -> a.length() - b.length()); System.out.println(words); Runnable task = () -> System.out.println("Задача выполнена"); task.run(); }}Формы записи:
(a, b) -> a + bx -> x * x() -> 42(a, b) -> { int sum = a + b; return sum * 2;}Лямбда может использовать локальные переменные из внешнего кода только если они final или фактически неизменяемые.
Стандартные функциональные интерфейсы
Пакет java.util.function:
| Интерфейс | Метод | Назначение |
|---|---|---|
Function<T, R> | R apply(T) | преобразование |
Predicate<T> | boolean test(T) | проверка условия |
Consumer<T> | void accept(T) | действие над значением |
Supplier<T> | T get() | поставщик значения |
BiFunction<T, U, R> | R apply(T, U) | функция двух аргументов |
UnaryOperator<T> | T apply(T) | преобразование в тот же тип |
BinaryOperator<T> | T apply(T, T) | свертка двух значений |
import java.util.function.Consumer;import java.util.function.Function;import java.util.function.Predicate;import java.util.function.Supplier;public class FunctionalDemo { public static void main(String[] args) { Function<String, Integer> length = String::length; Predicate<Integer> isEven = n -> n % 2 == 0; Consumer<String> printer = s -> System.out.println("> " + s); Supplier<Double> random = Math::random; System.out.println(length.apply("привет")); System.out.println(isEven.test(4)); printer.accept("текст"); System.out.println(random.get() < 1); Function<Integer, Integer> doubled = x -> x * 2; Function<Integer, Integer> plusOne = x -> x + 1; System.out.println(doubled.andThen(plusOne).apply(5)); System.out.println(doubled.compose(plusOne).apply(5)); Predicate<String> notEmpty = s -> !s.isEmpty(); Predicate<String> shortWord = s -> s.length() < 5; System.out.println(notEmpty.and(shortWord).test("abc")); System.out.println(notEmpty.negate().test("")); }}Ссылки на методы
| Вид | Запись | Эквивалент |
|---|---|---|
| статический метод | Integer::parseInt | s -> Integer.parseInt(s) |
| метод объекта | System.out::println | x -> System.out.println(x) |
| метод типа | String::toUpperCase | s -> s.toUpperCase() |
| конструктор | ArrayList::new | () -> new ArrayList<>() |
Optional
Контейнер для значения, которого может не быть:
import java.util.List;import java.util.Optional;public class OptionalDemo { static Optional<String> findFirstLong(List<String> words) { return words.stream().filter(w -> w.length() > 5).findFirst(); } public static void main(String[] args) { Optional<String> result = findFirstLong(List.of("кот", "слон", "крокодил")); System.out.println(result.isPresent()); System.out.println(result.get()); System.out.println(result.map(String::toUpperCase).orElse("нет")); result.ifPresent(w -> System.out.println("Найдено: " + w)); Optional<String> none = Optional.empty(); System.out.println(none.orElse("значение по умолчанию")); System.out.println(none.orElseGet(() -> "вычисленное значение")); }}get() без проверки бросит NoSuchElementException. Предпочитайте orElse, orElseGet, orElseThrow, ifPresent.
Stream API
Поток описывает цепочку операций над данными: создание, промежуточные операции, терминальная операция. Поток не хранит данные и не изменяет источник.
import java.util.List;public class StreamBasics { public static void main(String[] args) { List<Integer> numbers = List.of(5, 2, 8, 1, 9, 3, 6); List<Integer> result = numbers.stream() .filter(n -> n > 2) .map(n -> n * n) .sorted() .toList(); System.out.println(result); }}Вывод: [9, 25, 36, 64, 81].
Промежуточные операции выполняются лениво и только при вызове терминальной.
Создание потоков
import java.util.Arrays;import java.util.stream.IntStream;import java.util.stream.Stream;public class StreamCreate { public static void main(String[] args) { Stream<String> a = Stream.of("x", "y", "z"); Stream<Integer> b = Arrays.asList(1, 2, 3).stream(); IntStream c = IntStream.range(0, 5); IntStream d = IntStream.rangeClosed(1, 5); Stream<Integer> e = Stream.iterate(1, x -> x * 2).limit(8); IntStream f = Arrays.stream(new int[]{1, 2, 3}); System.out.println(e.toList()); System.out.println(d.sum()); }}Промежуточные операции
| Операция | Действие |
|---|---|
filter(Predicate) | оставить подходящие |
map(Function) | преобразовать каждый элемент |
flatMap(Function) | преобразовать и «сплющить» вложенные потоки |
distinct() | убрать дубликаты |
sorted() / sorted(Comparator) | отсортировать |
limit(n) / skip(n) | взять первые / пропустить первые |
peek(Consumer) | подсмотреть значение, для отладки |
Терминальные операции
| Операция | Результат |
|---|---|
forEach(Consumer) | выполнить действие |
collect(Collector) | собрать в коллекцию |
toList() | неизменяемый список |
count() | количество |
reduce(...) | свертка |
anyMatch, allMatch, noneMatch | проверка условий |
findFirst, findAny | Optional с элементом |
min, max | минимум и максимум по Comparator |
import java.util.List;public class StreamTerminal { public static void main(String[] args) { List<Integer> numbers = List.of(4, 7, 1, 9, 3); System.out.println(numbers.stream().count()); System.out.println(numbers.stream().anyMatch(n -> n > 8)); System.out.println(numbers.stream().allMatch(n -> n > 0)); System.out.println(numbers.stream().reduce(0, Integer::sum)); System.out.println(numbers.stream().max(Integer::compare).get()); System.out.println(numbers.stream().mapToInt(Integer::intValue).average().orElse(0)); }}Примитивные потоки
IntStream, LongStream, DoubleStream избегают упаковки и имеют методы sum, average, min, max, summaryStatistics:
import java.util.IntSummaryStatistics;import java.util.stream.IntStream;public class IntStreamDemo { public static void main(String[] args) { int sum = IntStream.rangeClosed(1, 100).sum(); System.out.println(sum); IntSummaryStatistics stats = IntStream.of(4, 8, 15, 16, 23, 42).summaryStatistics(); System.out.println(stats.getMin() + " " + stats.getMax() + " " + stats.getAverage()); }}Преобразования: mapToInt, mapToObj, boxed.
Collectors
import java.util.List;import java.util.Map;import java.util.Set;import java.util.stream.Collectors;public class CollectorsDemo { record Employee(String name, String department, double salary) { } public static void main(String[] args) { List<Employee> staff = List.of( new Employee("Анна", "IT", 3000), new Employee("Борис", "IT", 3500), new Employee("Вера", "HR", 2500), new Employee("Глеб", "HR", 2700), new Employee("Дина", "Sales", 2800) ); List<String> names = staff.stream() .map(Employee::name) .collect(Collectors.toList()); System.out.println(names); Set<String> departments = staff.stream() .map(Employee::department) .collect(Collectors.toSet()); System.out.println(departments); String joined = staff.stream() .map(Employee::name) .collect(Collectors.joining(", ", "[", "]")); System.out.println(joined); Map<String, List<Employee>> byDepartment = staff.stream() .collect(Collectors.groupingBy(Employee::department)); System.out.println(byDepartment.keySet()); Map<String, Long> counts = staff.stream() .collect(Collectors.groupingBy(Employee::department, Collectors.counting())); System.out.println(counts); Map<String, Double> avgSalary = staff.stream() .collect(Collectors.groupingBy(Employee::department, Collectors.averagingDouble(Employee::salary))); System.out.println(avgSalary); Map<Boolean, List<String>> partition = staff.stream() .collect(Collectors.partitioningBy( e -> e.salary() >= 2800, Collectors.mapping(Employee::name, Collectors.toList()) )); System.out.println(partition); Map<String, Double> nameToSalary = staff.stream() .collect(Collectors.toMap(Employee::name, Employee::salary)); System.out.println(nameToSalary); }}toMap бросит исключение при дублировании ключей. В этом случае передайте функцию слияния третьим аргументом.
flatMap
import java.util.Arrays;import java.util.List;import java.util.stream.Collectors;public class FlatMapDemo { public static void main(String[] args) { List<String> lines = List.of("раз два", "три четыре пять"); List<String> words = lines.stream() .flatMap(line -> Arrays.stream(line.split(" "))) .collect(Collectors.toList()); System.out.println(words); }}Параллельные потоки
import java.util.stream.IntStream;public class ParallelDemo { public static void main(String[] args) { long sum = IntStream.rangeClosed(1, 1_000_000) .parallel() .asLongStream() .sum(); System.out.println(sum); }}Параллельный поток дает выигрыш только на больших объемах данных и независимых операциях. Не используйте его с побочными эффектами и общим изменяемым состоянием.
Правила хорошего стиля
- Не изменяйте внешние переменные внутри лямбд потока.
- Не используйте поток один раз повторно: после терминальной операции он закрыт.
- Если цепочка получилась слишком сложной, разбейте ее на методы с понятными именами.
- Для простых циклов обычный
forчасто читаемее.
