Тема 20. Лямбды - BelyiZ/JavaCourses GitHub Wiki
- Функциональный интерфейс
- Реализация лямбд
- Особенности Лямбд
- Случаи использования Лямбд
- Список литературы/курсов
Lambda-выражения (Лямбда-выражения) – Это функция, которая может быть создана без принадлежности к какому-либо классу, передаваться как объект и выполняться по требованию. По сути это анонимные функции. Проще говоря, это метод без объявления, то есть без модификаторов доступа, возвращающие значение и имя. Они называются анонимными, потому что в отличие от функций, у них нет имён.
Лямбда-выражения представляет набор инструкций, которые можно выделить в отдельную переменную и затем многократно вызвать в различных местах программы.
Преимущества использования:
- Оптимизация количества строк кода. Уменьшение объема кода так как возможно создать экземпляр интерфейса с помощью функционального лямбда-выражения, а не с помощью анонимного класса. Лучшее решение в случае необходимости однократного вызова метода, так как сокращается время на объявление и написание метода без необходимости создавать класс.
- Поддержка последовательного и параллельного выполнения. Возможно использовать
API Java Streams
для выполнения последовательных или параллельных операций. - Подходит для реализации простых событий/обратных вызовов или в функциональном программировании с помощью
API Java Streams
. - Возможно отложенное выполнение (
deferred execution
). Определив однажды возможно вызывать лямбда-выражение при необходимости неопределенное количество раз в различных частях программы.
Основная идея заключается в том, что возможно использовать лямбды везде, где в противном случае вы бы использовали функциональные интерфейсы для передачи функций.
Лямбда-выражение не выполняется само по себе, а образует реализацию метода, определенного в функциональном интерфейсе. При этом важно, что функциональный интерфейс должен содержать только один единственный метод без реализации.
Функциональный интерфейс — это интерфейс, который содержит ровно один абстрактный метод, то есть описание метода без тела. Статические методы и методы по умолчанию в функциональном интерфейсе могут быть в большом количестве.
Когда параметром метода является функциональный интерфейс, при вызове этого метода одним из аргументов должен быть блок кода.
Чтобы отметить интерфейс как функциональный, используется аннотация @FunctionalInterface
.
Передаваемый блок кода должен удовлетворять следующему условию: его сигнатура должна совпадать с сигнатурой единственного абстрактного метода функционального интерфейса.
На данный момент в Java 8
есть множество функциональных интерфейсов, которые находятся в пакете java.util.function
.
Наиболее полезными являются интерфейсы Consumer
, Supplier
, Function
и Predicate
.
Лямбда-выражения являются "синтаксическим сахаром" для объектов, реализующих единый интерфейс метода. В этих объектах реализация лямбда считается реализацией указанного метода. Если лямбда-код и интерфейс совпадают, то лямбда-функция может быть назначена переменной типа этого интерфейса.
Чтобы сопоставить лямбда-код с одним интерфейсом метода (функциональным интерфейсом), необходимо выполнить несколько условий:
- Функциональный интерфейс должен иметь ровно один нереализованный метод, и этот метод должен быть абстрактным. Интерфейс может содержать статические методы и методы по умолчанию, реализованные в нем, но должен существовать ровно один абстрактный метод.
- Абстрактный метод должен принимать аргументы в том же порядке, которые соответствуют параметрам, принимаемым лямбдой. Параметры выражения должны соответствовать параметрам одного метода
- Тип возвращаемого значения как метода, так и лямбда-функции должны совпадать. То есть тип возврата выражения должен соответствовать типу возврата одиночного метода
Если условия для сопоставления выполнены,тогда возможно назначить свою лямбду переменной.
Интерфейсы со стандартными и статическими методами
Начиная с Java 8, интерфейс Java может содержать как стандартные, так и статические методы. Они имеют реализацию, определенную непосредственно в объявлении интерфейса. Это означает, что лямбда может реализовывать интерфейсы с более чем одним методом – при условии, что интерфейс имеет только один не реализованный (абстрактный) метод.
Другими словами, интерфейс по-прежнему является функциональным интерфейсом, даже если он содержит стандартные и статические методы Java, но только один не реализованный (абстрактный) метод.
Пример:
import java.io.IOException;
import java.io.OutputStream;
public interface MyInterface {
void printIt(String text);
}
default public void printUtf8To(String text, OutputStream outputStream){
try {
outputStream.write(text.getBytes("UTF-8"));
} catch(IOException e) {
throw new RuntimeException("Error writing String as UTF-8 to OutputStream", e);
}
}
static void printItToSystemOut(String text){
System.out.println(text);
}
Может быть переписан:
MyInterface myInterface =(String text) -> {
System.out.print(text);
};
Реализация любого лямбда-выражения базируется на использовании двух языковых конструкций:
- непосредственно лямбда-выражения;
- функционального интерфейса.
Лямбда-выражение — это анонимный (безымянный) метод, который не выполняется самостоятельно, а служит реализацией метода, объявленного в функциональном интерфейсе. Если в программе встречается лямбда-выражение, то это приводит к созданию некоторого анонимного класса, содержащего анонимный метод, код которого определяется в лямбда-выражении.
При объявлении лямбда-выражения используется лямбда-оператор, который обозначается символами "–>" (стрелка). Лямбда-оператор трактуется как "становится" или "переходит". Лямбда-оператор (–>) разделяет лямбда-выражение на две части: левую и правую. В левой части лямбда-выражения указывается список параметров выражения. Правая часть представляет тело лямбда-выражения - указываются действия (операторы), которые определяют код лямбда-выражения.
Синтаксис:
(аргументы) -> (тело).
Основная идея лямбда – функций та же, что и основная идея методов-они принимают параметры и используют их в теле, состоящем из выражений.
Лямбда-выражение не выполняется само по себе, а образует реализацию метода, определенного в функциональном интерфейсе. При этом важно, что функциональный интерфейс должен содержать только один единственный метод без реализации.
Код лямбда-выражения может формироваться одним из двух способов:
- содержать одиночное выражение. В этом случае лямбда-выражение называется одиночным;
(list_of_parameters) -> System.out.println("Lambdas");
- выражения, заключенные в фигурные скобки { }. Это случай, когда в правой части лямбда-оператора нужно указать несколько операторов.
В этом случае лямбда-выражение называется блочным. При возврате результата в блочных лямбда-выражениях обязательно нужно указывать оператор
return
.
() -> {
double pi = 3.1415;
return pi;
};
(list_of_parameters) -> { // (арг1, арг2...) -> { тело }
// Тело лямбда-выражения
// ...
return result;
}
list_of_parameters
– список параметров, которые получает лямбда-выражение. Список параметров указывается через запятую точно также как в методе.
Список параметров указывается в левой части лямбда-оператора.
Параметры лямбда-выражения должны быть совместимы по типу и количеству с параметрами абстрактного метода, который объявлен в функциональном интерфейсе.
Пример: Лямбда-выражение, которое получает два целочисленных параметра и возвращает их произведение.
(int a, int b) -> a*b
Пример: Лямбда-выражение, которое по длинам трех сторон a, b, c возвращает площадь треугольника.
(double a, double b, double c ) -> {
if (((a+b)<c) || ((a+c)<b) || ((b+c)<a))
return 0.0;
else
{
double p = (a+b+c)/2; // полупериметер
double s = Math.sqrt(p*(p-a)*(p-b)*(p-c)); // площадь по формуле Герона
return s;
}
}
Если в лямбда-выражении содержится единственный параметр, он может быть без круглых скобок ( ):
i -> {
// ...
}
Возможна ситуация, когда лямбда-выражение не получает параметров. В этом случае общая форма блочного лямбда-выражения следующая:
() -> {
// Тело лямбда-выражения
// ...
}
Если в лямбда-выражении используется один оператор (выражение), то фигурные скобки можно опустить:
(list_of_parameters) -> expression;
list_of_parameters
— список параметров метода;
expression
– выражение, которое будет вычислено при использовании лямбда-выражения.
Пример: Ниже приведено лямбда-выражение без параметров, которое возвращает число π:
() -> Math.PI
В вышеприведенном коде результатом лямбда-выражения служит константа PI из класса Math.
Вывод: () -> 3.1415
Захват переменных позволяет лямбдам использовать переменные, объявленные вне самой лямбды.
Существует три типа захвата переменных:
- захват локальной переменной
- захват переменной экземпляра
- захват статической переменной
Синтаксис аналогичен получению доступа к этим переменным из любой другой функции, но условия, при которых возможно осуществить доступ - отличаются.
Возможно получить доступ к локальной переменной только в том случае, если она фактически окончательная, что означает, что она не изменяет свое значение после присвоения. Нет требования, что объявление должно быть "окончательное". В случае использования переменной в лямбда-функции и изменения значения, компилятор отреагирует. Это связано с тем, что лямбда-код не может надежно ссылаться на локальную переменную, поскольку она может быть уничтожена до выполнения лямбды. Из-за этого появляется глубокая копия. Изменение локальной переменной может привести к некоторому запутанному поведени. Чтобы избежать путаницы, это явно запрещено.
Когда дело доходит до переменных экземпляра, то если лямбда находится в том же классе, что и переменная, к которой обращается код, возможно иметь доступ к полю в этом классе. Кроме того, поле не обязательно должно быть окончательным и может быть изменено позже в ходе действия программы.
Это связано с тем, что если лямбда-код определен в классе, он создается вместе с этим классом и привязан к этому экземпляру класса и, таким образом, может легко ссылаться на значение нужного ему поля.
Статические переменные записываются так же, как переменные экземпляра. Они могут быть изменены и не обязательно должны быть окончательными по тем же причинам.
- Являются анонимными классами, реализующими метод функционального интерфейса.
- Имеют доступ только к
final
(илиeffectively final
) переменным из охватывающей области видимости (для потокобезопасности). - Не могут возвращать значение в каких-то ветках, а в других не возвращать.
- Позволяют уменьшить количество кода и повысить его читаемость.
- Обход элементов в цикле
List<String> players = Arrays.asList("Алексей Петров", "Сергей Забивалов", "Николай Башкоймяч");
players.forEach(item -> System.out.println(item));
- Применяют в компараторах при сортировке. Допустим, нужно отсортировать коллекцию по последней букве каждого слова.
List<String> players = Arrays.asList("Алексей Петров", "Сергей Забивалов", "Николай Башкоймяч");
Collections.sort(players, (o1, o2) -> {
String o1LastLetter = o1.substring(o1.length() - 1);
String o2LastLetter = o2.substring(o2.length() - 1);
return o1LastLetter.compareTo(o2LastLetter);
});
- Применяют при работе с коллекциями вместе со
Stream API
. Фильтруем стрим по значениюfilter
, меняем каждый элементmap
и собираем в списокcollect
.
final double targetPoints = 80;
List<Double> playerPoints = Arrays.asList(25d, 50d , 60d, 12d, 45d, 89d);
List<Double> targetPointsGreater50 = playerPoints.stream()
.filter(d -> d > 50) // используем функциональный интерфейс Predicate<T>
.map(d -> d / targetPoints) // используем функциональный интерфейс Function<T, R>
.collect(Collectors.toList());
- https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html
- https://habr.com/ru/post/512730/
- https://javarush.ru/groups/posts/lambda-vihrazhenija-v-java-chast-1
Тема 19. Множества | Оглавление | Тема 21. Указатели на методы