Тема 20. Лямбды - BelyiZ/JavaCourses GitHub Wiki

Содержание:

  1. Функциональный интерфейс
  2. Реализация лямбд
    1. Лямбды как объекты
    2. Согласование интерфейса
    3. Реализация
    4. Захват переменных
  3. Особенности Лямбд
  4. Случаи использования Лямбд
  5. Список литературы/курсов

Lambda-выражения (Лямбда-выражения) – Это функция, которая может быть создана без принадлежности к какому-либо классу, передаваться как объект и выполняться по требованию. По сути это анонимные функции. Проще говоря, это метод без объявления, то есть без модификаторов доступа, возвращающие значение и имя. Они называются анонимными, потому что в отличие от функций, у них нет имён.

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

Преимущества использования:

  1. Оптимизация количества строк кода. Уменьшение объема кода так как возможно создать экземпляр интерфейса с помощью функционального лямбда-выражения, а не с помощью анонимного класса. Лучшее решение в случае необходимости однократного вызова метода, так как сокращается время на объявление и написание метода без необходимости создавать класс.
  2. Поддержка последовательного и параллельного выполнения. Возможно использовать API Java Streams для выполнения последовательных или параллельных операций.
  3. Подходит для реализации простых событий/обратных вызовов или в функциональном программировании с помощью API Java Streams.
  4. Возможно отложенное выполнение (deferred execution). Определив однажды возможно вызывать лямбда-выражение при необходимости неопределенное количество раз в различных частях программы.

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

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

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

Функциональный интерфейс — это интерфейс, который содержит ровно один абстрактный метод, то есть описание метода без тела. Статические методы и методы по умолчанию в функциональном интерфейсе могут быть в большом количестве.

Когда параметром метода является функциональный интерфейс, при вызове этого метода одним из аргументов должен быть блок кода. Чтобы отметить интерфейс как функциональный, используется аннотация @FunctionalInterface.

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

На данный момент в Java 8 есть множество функциональных интерфейсов, которые находятся в пакете java.util.function. Наиболее полезными являются интерфейсы Consumer, Supplier, Function и Predicate.

Реализация Лямбда

Лямбды как объекты

Лямбда-выражения являются "синтаксическим сахаром" для объектов, реализующих единый интерфейс метода. В этих объектах реализация лямбда считается реализацией указанного метода. Если лямбда-код и интерфейс совпадают, то лямбда-функция может быть назначена переменной типа этого интерфейса.

Согласование интерфейса

Чтобы сопоставить лямбда-код с одним интерфейсом метода (функциональным интерфейсом), необходимо выполнить несколько условий:

  1. Функциональный интерфейс должен иметь ровно один нереализованный метод, и этот метод должен быть абстрактным. Интерфейс может содержать статические методы и методы по умолчанию, реализованные в нем, но должен существовать ровно один абстрактный метод.
  2. Абстрактный метод должен принимать аргументы в том же порядке, которые соответствуют параметрам, принимаемым лямбдой. Параметры выражения должны соответствовать параметрам одного метода
  3. Тип возвращаемого значения как метода, так и лямбда-функции должны совпадать. То есть тип возврата выражения должен соответствовать типу возврата одиночного метода

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

Интерфейсы со стандартными и статическими методами

Начиная с 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);
};

Реализация

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

  1. непосредственно лямбда-выражения;
  2. функционального интерфейса.

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

При объявлении лямбда-выражения используется лямбда-оператор, который обозначается символами "–>" (стрелка). Лямбда-оператор трактуется как "становится" или "переходит". Лямбда-оператор (–>) разделяет лямбда-выражение на две части: левую и правую. В левой части лямбда-выражения указывается список параметров выражения. Правая часть представляет тело лямбда-выражения - указываются действия (операторы), которые определяют код лямбда-выражения.

Синтаксис:

(аргументы) -> (тело).

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

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

Код лямбда-выражения может формироваться одним из двух способов:

  1. содержать одиночное выражение. В этом случае лямбда-выражение называется одиночным;
(list_of_parameters) -> System.out.println("Lambdas");
  1. выражения, заключенные в фигурные скобки { }. Это случай, когда в правой части лямбда-оператора нужно указать несколько операторов. В этом случае лямбда-выражение называется блочным. При возврате результата в блочных лямбда-выражениях обязательно нужно указывать оператор 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

Захват переменных

Захват переменных позволяет лямбдам использовать переменные, объявленные вне самой лямбды.

Существует три типа захвата переменных:

  1. захват локальной переменной
  2. захват переменной экземпляра
  3. захват статической переменной

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

Возможно получить доступ к локальной переменной только в том случае, если она фактически окончательная, что означает, что она не изменяет свое значение после присвоения. Нет требования, что объявление должно быть "окончательное". В случае использования переменной в лямбда-функции и изменения значения, компилятор отреагирует. Это связано с тем, что лямбда-код не может надежно ссылаться на локальную переменную, поскольку она может быть уничтожена до выполнения лямбды. Из-за этого появляется глубокая копия. Изменение локальной переменной может привести к некоторому запутанному поведени. Чтобы избежать путаницы, это явно запрещено.

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

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

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

Особенности Лямбд

  1. Являются анонимными классами, реализующими метод функционального интерфейса.
  2. Имеют доступ только к final (или effectively final) переменным из охватывающей области видимости (для потокобезопасности).
  3. Не могут возвращать значение в каких-то ветках, а в других не возвращать.
  4. Позволяют уменьшить количество кода и повысить его читаемость.

Случаи использования Лямбд

  1. Обход элементов в цикле
List<String> players = Arrays.asList("Алексей Петров", "Сергей Забивалов", "Николай Башкоймяч");
players.forEach(item -> System.out.println(item));
  1. Применяют в компараторах при сортировке. Допустим, нужно отсортировать коллекцию по последней букве каждого слова.
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);
});
  1. Применяют при работе с коллекциями вместе со 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()); 

Список литературы/курсов

  1. https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html
  2. https://habr.com/ru/post/512730/
  3. https://javarush.ru/groups/posts/lambda-vihrazhenija-v-java-chast-1

Тема 19. Множества | Оглавление | Тема 21. Указатели на методы

⚠️ **GitHub.com Fallback** ⚠️