JavaRush /Курсы /Java Syntax Pro /Функциональный метод

Функциональный метод

Java Syntax Pro
18 уровень, 1 лекция
Открыта

1. Функциональные методы

Если у интерфейса есть только один метод, переменной этого типа-интерфейса можно присвоить значение, заданное лямбда-выражением (лямбда-функцией). Такие интерфейсы стали называть функциональными интерфейсами (после добавления в Java поддержки лямбда-функций).

Например, в Java есть интерфейс Consumer<Тип> (Consumer == Потребитель), который содержит метод accept(Тип obj). Зачем же нужен этот интерфейс?

В Java 8 у коллекций появился метод forEach(), который позволяет для каждого элемента коллекции выполнить какое-нибудь действие. И вот для передачи действия в метод forEach() как раз и используется функциональный интерфейс Consumer<T>.

Вот как можно вывести все элементы коллекции на экран:

ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "Привет", "как", "дела?");

list.forEach( (s) -> System.out.println(s) );
Вывод всех элементов коллекции (с использованием лямбда-выражения)

Компилятор преобразует этот код в код:

ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "Привет", "как", "дела?");

list.forEach(new Consumer<String>()
{
   public void accept(String s)
   {
      System.out.println(s);
   }
});
Вывод всех элементов коллекции (запись с использованием анонимного класса)

Первая запись однозначно короче, чем вторая. И хотя читать код с лямбда-выражениями непросто, читать код с анонимными внутренними классами порой еще сложнее.



2. Ссылка на метод

Однако наш код с лямбда-выражением можно записать еще короче.

Во-первых, можно опустить скобки вокруг параметра s:

list.forEach( (s) -> System.out.println(s) );
Было
list.forEach( s -> System.out.println(s) );
Стало

Так можно делать только если параметр один. Если параметров несколько, нужно использовать скобки.

Ну а во-вторых, можно записать так:

list.forEach( System.out::println );
Самая компактная запись

Это все одна и та же запись. Обратите внимание, что после println нет скобок.

Тут записан один и тот же код — вызов метода:

объект::метод
x -> объект.метод(x)

Подумайте сами: мы хотели для каждого элемента коллекции list выполнять какое-то действие. Если это действие — вызов одной функции (такой как println()), было бы разумно просто передать функцию в метод в качестве параметра.

А как объяснить компилятору, что функцию нужно именно передать, а не вызвать? Для этого перед именем метода ставим не точку, а два двоеточия: одно двоеточие уже занято в тернарном операторе.

Это и есть самая простая и компактная запись.



3. Конструктор

Ссылки на методы с помощью двойного двоеточия очень удобно использовать, когда мы будем работать с потоками ввода-вывода. Вы в этом убедитесь чуть позже

А пока давайте поговорим о 3 популярных способах передачи ссылки на метод:

Ссылка на метод объекта

Чтобы передать ссылку на метод объекта, нужно записать код вида объект::метод.
Этот код эквивалентен коду x -> объект.метод(x).

В качестве объекта могут фигурировать такие специальные переменные как this и super.

Ссылка на метод класса

Чтобы передать ссылку на статический метод, нужно записать код вида класс::метод. Этот код будет преобразован к коду вида x -> класс.метод(x);

Ссылка на конструктор

Конструктор по своему поведению чем-то похож на статический метод класса, поэтому на него тоже можно передать ссылку. Выглядит это так: класс::new.

Например, можно обойти стирание типов у коллекций и передать в метод toArray() ссылку на конструктор, который создаст нужный массив: toArray( int[]::new );


Комментарии (286)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Euraks Уровень 33
8 октября 2026
как то быстро лямбды и функциональные интерфейсы проскочили
Grigoryvvv Уровень 28 Expert
23 июля 2026
27.07.2026 / 19 уровень 1. Происхождение метода forEach() В данной лекции речь идет именно о Методе forEach, а не об обычном Цикле for-each. Это разные понятия. Вспомним структуру наследования коллекций: Iterable │ ├── содержит - default void forEach(...) {...} (готовая default-реализация) │ Collection │ ├── List │ └── ArrayList │ ├── Set │ └── HashSet ... Начиная с Java 8, интерфейсы могут содержать default-методы — методы с уже готовой реализацией. Классы-наследники не обязаны переопределять готовую реализацию дефолтных методов интерфейсов. Они их просто наследуют, меняя реализацию только если есть необходимость. Интерфейс Iterable объявляет дефолтный метод с готовой реализацией forEach(). Поэтому все классы реализующие Iterable, наследуют готовую реализацию метода forEach(). Это хороший пример того, как интерфейсы с default-методами позволяют добавлять общую функциональность сразу всем реализующим классам. Поэтому любая коллекция ArrayList, HashSet, ... может вызывать метод forEach() 2. Разбор метода forEach() Реализация метода:

default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}
- метод forEach в качестве аргумента принимает объект, реализующий интерфейс Consumer; - <? super T> - принимает Consumer типа T или любой его родительский тип (Object, .. и т.д.); - Objects.requireNonNull(action); — это проверка, что переданный объект не равен null; - внутри метода forEach используется обычный цикл for-each; - this - это текущая коллекция (ArrayList, HashSet и т.д.); - action.accept(t); вызывается метод accept() интерфейса Consumer для каждого элемента коллекции. Интерфейс Consumer:

public interface Consumer<T> {
    void accept(T t);
}
Grigoryvvv Уровень 28 Expert
23 июля 2026
метод forEach() отвечает только за перебор элементов. Consumer отвечает за что делать с каждым элементом. 3. Применение метода forEach() Мы можем задать что должен делать каждый элемент нашей коллекции. Это можно делать разными способами. Допустим у нас есть список:

ArrayList < String > list = new ArrayList <> ();
Collections.addAll(list, "Привет", "как", "дела?");
● Самый трудоемкий способ. Создавать отдельный класс реализующий интерфейс Consumer. Реализовать в этом классе метод accept(). Пример:

class PrintConsumer<T> implements Consumer<T> {
    public void accept(T t) {
        System.out.println(t);
    }
}
затем вызвать метод forEach и передать в аргументы объект Consumer типа T.

list.forEach(new PrintConsumer<T>());
● Более простой способ. С помощью синтаксического сахара создать лямбда-выражение содержащее реализацию метода accept(). И это лямбда-выражение передать в качестве аргумента в метод forEach.

list.forEach(s -> System.out.println(s));
- System.out.println(s) - это как раз переданная реализация метода accept() Для простоты будем считать, что лямбда-выражение само создает скрытый класс и реализует в нем метод accept(). Как мы получаем лямбда-выражение советую посмотреть в предыдущей лекции. Конечно же вы не будете помнить как сокращают код до готового лямбда-выражения с помощью синтаксического сахара. На практике все запоминают уже готовые лямбда-выражения и меняют в них только тело реализации. ● Еще более короткая запись. Ссылка на метод. Вместо лямбда-выражения можно использовать ссылку на уже существующий метод:

list.forEach(System.out::println);
System.out::println — это ссылка на метод println() объекта System.out.
Grigoryvvv Уровень 28 Expert
25 июля 2026
:: оператор ссылки на метод. Он не дает методу сразу выполнится (не вызывает метод). Оператор ссылки на метод только получает ссылку на существующий метод, чтобы её можно было передать куда-то дальше. Ссылка на метод это не лямбда-выражение, но альтернатива лямбда-выражению. Компилятор может преобразовать ссылку на метод в реализацию функционального интерфейса так же, как лямбда-выражение. Метод forEach() ожидает Consumer<String>, то есть что-то с методом void accept(String s); А у нас есть System.out::println → System.out.println(String) Сигнатуры подходят: Consumer.accept(String) ↔ System.out.println(String) Поэтому Java говорит: «Хорошо, этот метод подходит. Используем его как реализацию Consumer». Можно запомнить четыре основных формы ссылок на методы:

1. Ссылка на статический метод класса

Integer::parseInt

аналог:

s -> Integer.parseInt(s)


2. Ссылка на метод конкретного объекта

System.out::println

аналог:

s -> System.out.println(s)


3. Ссылка на нестатический метод объекта через параметр

Например:

String::length

аналог:

s -> s.length()


4. Ссылка на конструктор

ArrayList::new

аналог:

() -> new ArrayList<>()


Dmitry Gisinov Уровень 25
15 марта 2026
Хочу оставить отзыв. Тут очень всё понятно, задачи простые и помогают закрепить правило. Я думаю мне помогло то, что я недавно пробежался по теме с циклами.
Андрей Руденко Уровень 21
13 октября 2025
Очень интересно но не понятно НИЧЕГО
Виктор Уровень 28
29 сентября 2025
Что-то получилось, а как потом разберемся
Руслан Уровень 48
23 сентября 2025
Ох ну и ж....
Anonymous #3585174 Уровень 33
23 июля 2025
like
Рифати Уровень 2
5 июля 2025
Exaltyr777 Уровень 25
9 августа 2025
Есть такое
ShadyLine Уровень 51
6 апреля 2025
Хорошая увлекательная тема, но полагаю сложно будет человеку читать все эти лямбда-функции...
7 апреля 2025
как правило это читается намного легче, потому что кода меньше
Vitalii Shevchenko Уровень 1
19 марта 2025
Стирання типів — це процес, під час якого загальні (generic) типи стираються під час компіляції, і в байт-коді залишаються лише об'єктні типи (Object або межові типи). Це зроблено для збереження зворотної сумісності з версіями Java до 5, коли generics ще не існували.