Лямбда выражения

lambdaCреди прочих нововведений в примере Hello,Streams  был показан код, фильтрующий коллекцию:

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

Полный синтаксис лямбда выражений позволяет типизировать переменные, задавать им модификаторы или аннотации и использовать сложные конструкции в коде:

Синтаксис может быть упрощён: если в коде ровным счётом одно выражение, фигурные скобки вокруг кода могут быть опущены:

В случае единственного выражения, это выражение будет исполнено, а результат его исполнения автоматически возвращён из функции. Тип этого результата вычисляется автоматически.

В большинстве случаев тип параметров так же может быть определён автоматически и его можно пропустить:

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

Функциональные интерфейсы

Лямбда-выражения может быть использовано там, где ожидается функциональный интерфейс, то есть интерфейс, реализующий единственный абстрактный метод. Тут надо отметить, что в 8 появились интерфейсы, содержащие в себе код (и, следовательно, множественное наследование). Да и в ранних версиях языка можно было переопределять такие методы, как toString() прямо в интерфейсе.

Аннотация @FunctionalInterface  говорит компилятору о ваших намерениях и позволяет проверить интерфейс на соответствие требованиям на этапе компиляции. Функциональный интерфейс можно использовать в качестве типа переменной, передаваемой в функцию:

При это фактически параметром функции выступает некоторое лямбда-выражение, совместимое с единственным методом интерфейса по типу:

Такой подход к передаче лямбда-выражений c использованием функциональных интерфейсов немного напоминает реализацию интерфейсов в языке Go: там тоже отделены код, реализация и интерфейс и они ничего не знают друг о друге.

В Java8 имеется некоторое количество предопределённых функциональных интерфейсов, которые находятся в пакете java.util.function. Например, там определён интерфейс  Predicate<T>, который ожидает метод filter(). Использование стандартных функциональных интерфейсов позволяет отказаться от доморощенного интерфейса, из примера выше, и тем самым упростить код и сделать его более читабельным:

Обратите внимание, что хоть интерфейс и изменился, лямбда-выражение осталось тем же самым.

Доступ к переменным и замыкания

Лямбда-выражения имеют доступ к переменным области видимости, в которой их определили:

Но доступ возможен только при условии, что переменные являются effective final, то есть либо явно имеют модификатор final, либо не меняют своего значения после инициализации.

С другой стороны, в java8 есть замыкания, то есть лямбда-выражение может использовать переменные уже после того, как действие их области видимости закончилось:

Очевидно, что к моменту использования лямбда-выражения локальная переменная length уже вышла из своей области видимости и превратилась в тыкву, но её значение осталось запомненным в конкретном экземпляре лямбда-выражения.

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

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

В данном коде  String::compareToIgnoreCase эквивалентно лямбда-выражению (o,str) -> o.compareToIgnoreCase(str)

Аналогичным образом можно ссылаться на статические методы класса или на методы конкретного экземпляра класса:

В случае, когда ссылаются на не статический метод класса, подразумеваемый первый аргумент подразумеваемого лямбда выражения будет экземпляром этого класса, как показано в примере с сортировкой. Очевидно, что при использовании ссылок на методы вместо лямбда выражений требуется, чтобы методы были совместимыми по типу с ожидаемым интерфейсом и с учётом правил передачи переменных, описанных выше.

Так же можно ссылаться на методы объектов this или super, а так же на конструкторы Class::new. Последняя возможность широко используется при работе с потоковыми данными, которая будет описана в отдельной статье.

Код примера доступен на github.