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 );
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ