3 марта 2025
Тэги: Java, Stream API, ООП.
В основе Stream API, которое значительно упрощает работу с коллекциями в Java, лежит понятие функциональных интерфейсов.
Любой интерфейс можно назвать функциональным, если он содержит один-единственный метод. Такой интерфейс снабжается аннотацией @FunctionalInterface.
Например, мы можем создать обобщённый функциональный интерфейс Convertable, который содержит метод convert(). В качестве параметра метод принимает на вход объект типа A и возвращает объект типа B.
Тогда мы можем формировать реализацию этого интерфейса «на лету» с помощью лямбда-выражений и передавать лямбду в качестве параметра в другой метод:
Поскольку параметр converter метода doConvert() типизирован типами String и Integer, мы в методе run() можем указать любую лямбду, которая принимает целое число и возвращает строку. В данном случае мы увидим в консоли квадрат числа 2.
Лямбду можно представлять как более краткую запись анонимного класса (хотя с точки зрения компилятора это не совсем так).
Лямбда может содержать и более одного параметра, но если входной параметр только один и есть метод, удовлетворяющий такой сигнатуре, то можно использовать ещё более краткую запись:
Такая форма записи через двойное двоеточие называется «method reference».
Как видите, лямбда позволяет компактно записать реализацию интерфейса. Особенно если эта реализация представляет собой краткую формулу без дополнительных условий. И это очень мощная концепция, которая активно используется в Stream API.
Рассмотрим наиболее употребимые функциональные интерфейсы стандартной библиотеки. Все эти интерфейсы находятся в пакете java.util.function.
Функциональный интерфейс Consumer содержит метод accept(). Этот метод принимает объект типа T и ничего не возвращает:
Именно такой интерфейс принимает метод Stream.forEach(). Внутри него метод accept() последовательно применяется к каждому элементу стрима.
В данном случае мы передаём метод println() через method reference и выводим все элементы стрима.
Обратным к интерфейсу Consumer можно назвать Supplier. Он содержит метод get(), который ничего не принимает, но возвращает результат.
В следующем примере через лямбду мы создаём Supplier, который генерирует случайные числа в диапазоне от 0 до 100 (не включая верхнюю границу диапазона). Его передаём в метод Stream.generate() и далее с помощью limit() ограничиваем стрим в 10 элементов, а затем выводим все числа на экран:
В консоли мы увидим 10 случайных чисел.
Функциональный интерфейс Predicate содержит метод test(). Он принимает на вход объект типа T, проверяет его по некоторому условию и возвращает результат проверки в виде boolean:
Этот интерфейс в виде лямбды можно передавать в метод Stream.filter(). Например, вот так можно вывести все чётные числа стрима:
Помимо filter(), этот же функциональный интерфейс принимают методы allMatch(), anyMatch() и noneMatch().
Здесь мы проверяем, являются ли все числа стрима положительными.
Функциональный интерфейс Function является базовым для целого семейства функциональных интерфейсов. Он содержит метод apply(), который принимает на вход объект типа T и преобразует его в объект типа R.
По сути делает ровно то же, что и наш интерфейс Convertable, который мы рассмотрели в начале статьи.
Именно Function принимает на вход метод Stream.map():
В этом примере мы выводим квадраты исходных чисел в виде строк.
Частным случаем интерфейса Function является UnaryOperator. У него совпадают типы входного параметра и результата.
Модуль числа или квадратный корень можно представить как унарные операторы:
Функциональный интерфейс, принимающий на вход два параметра разных типов и возвращающий результат, называется BiFunction.
По аналогии с UnaryOperator, частный случай BiFunction, у которого совпадают типы обоих параметров и выходного значения, называется BinaryOperator:
Стандартные методы вроде возведения в степень или конкатенация строк могут быть представлены как бинарные операторы:
В этой статье мы познакомились с функциональными интерфейсами в Java и научились записывать их в виде лямбда-выражений. Также мы рассмотрели основные функциональные интерфейсы из стандартной библиотеки, которые активно используются в Stream API. Другие интерфейсы (BooleanSupplier, DoublePredicate, IntFunction, LongConsumer и т.п.) являются частными случаями рассмотренных выше.
Kotlin, Java, Spring, Spring Boot, Spring Data, SQL, PostgreSQL, Oracle, H2, Linux, Hibernate, Collections, Stream API, многопоточность, чат-боты, нейросети, файлы, devops, Docker, Nginx, Apache, maven, gradle, JUnit, YouTube, новости, руководство, ООП, алгоритмы, головоломки, rest, GraphQL, Excel, XML, json, yaml.