Java. Лямбда-выражения. Основные понятия. Функциональный интерфейс. Обобщенные функциональные интерфейсы и лямбда-выражения. Примеры
Содержание
- 1. Понятие о лямбда-выражении. Преимущества применения лямбда-выражений
- 2. Лямбда-выражение (замыкание). Общая форма. Лямбда-оператор ->. Примеры
- 3. Функциональный интерфейс. Определение. Общая форма. Примеры
- 4. Последовательность шагов при построении лямбда-выражений
- 5. Решение задач на лямбда-выражения
- Связанные темы
Поиск на других ресурсах:
1. Понятие о лямбда-выражении. Преимущества применения лямбда-выражений
Лямбда-выражения появились в версии JDK 8 с целью усовершенствования языка Java. Лямбда-выражения еще называют замыканиями.
К преимуществам применения лямбда-выражений в языке Java можно отнести следующие:
- использование новых элементов, повышающих выразительность языка Java. Упрощается реализация некоторых общеупотребительных конструкций;
- расширение возможностей библиотеки программного интерфейса (API). Сюда можно отнести упрощение параллельной обработки, улучшение работы с потоками ввода/вывода в программном интерфейсе API.
Появление лямбда-выражений способствовала возникновению таких новых средств языка Java, как методы по умолчанию и использование ссылок на метод без его выполнения.
⇑
2. Лямбда-выражение (замыкание). Общая форма. Лямбда-оператор ->. Примеры
Реализация любого лямбда-выражения базируется на использовании двух языковых конструкций:
- непосредственно лямбда-выражения;
- функционального интерфейса.
Лямбда-выражение — это анонимный (безымянный) метод, который не выполняется самостоятельно, а служит реализацией метода, объявленного в функциональном интерфейсе (смотрите пункт 3). Если в программе встречается лямбда-выражение, то это приводит к созданию некоторого анонимного класса, содержащего анонимный метод, код которого определяется в лямбда-выражении.
При объявлении лямбда-выражения используется лямбда-оператор, который обозначается символами –> (стрелка). Лямбда-оператор трактуется как «становится» или «переходит». Лямбда-оператор (–>) разделяет лямбда-выражение на две части: левую и правую. В левой части лямбда-выражения указываются параметры. В правой части указываются действия (операторы), которые определяют код лямбда-выражения.
Код лямбда-выражения может формироваться одним из двух способов:
- содержать одиночное выражение. В этом случае лямбда-выражение называется одиночным;
- заключенный в фигурные скобки { }. Это случай, когда в правой части лямбда-оператора нужно указать несколько операторов. В этом случае лямбда-выражение называется блочным. При возврате результата в блочных лямбда-выражениях обязательно нужно указывать оператор return.
В простейшем случае, объявление блочного лямбда-выражения может быть следующим:
(list_of_parameters) -> { // Тело лямбда-выражения // ... return result; }
здесь
- list_of_parameters – список параметров, которые получает лямбда-выражение. Список параметров указывается через запятую точно также как в методе. Список параметров указывается в левой части лямбда-оператора. Параметры лямбда-выражения должны быть совместимы по типу и количеству с параметрами абстрактного метода, который объявлен в функциональном интерфейсе.
Если в лямбда-выражении содержится единственный параметр, он может быть без круглых скобок ( ):
i -> {
// ...
}
Возможна ситуация, когда лямбда-выражение не получает параметров. В этом случае общая форма блочного лямбда-выражения следующая
() -> { // Тело лямбда-выражения // ... }
Если в лямбда-выражении используется один оператор (выражение), то фигурные скобки можно опустить:
(list_of_parameters) -> expression;
здесь
- list_of_parameters — список параметров метода;
- expression – выражение, которое будет вычислено при использовании лямбда-выражения.
Пример 1. Ниже приведено лямбда-выражение без параметров, которое возвращает число π:
() -> Math.PI
В вышеприведенном коде результатом лямбда-выражения служит константа PI из класса Math. Можно было написать другое решение
() -> 3.1415
Пример 2. Лямбда-выражение, которое получает два целочисленных параметра и возвращает их произведение.
(int a, int b) -> a*b
Приклад 3. Лямбда-выражение, которое по длинам трех сторон 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; } }
⇑
3. Функциональный интерфейс. Определение. Общая форма. Примеры
Функциональный интерфейс — это интерфейс, который содержит только один абстрактный метод. Функциональный интерфейс определяет только одно действие (операцию). В функциональном интерфейсе определяется целевой тип лямбда-выражения. Функциональный интерфейс еще называют SAM-типом (Single Abstract Method).
В наиболее общем случае объявления функционального интерфейса выглядит следующим образом:
interface InterfaceName {
return_type MethodName(list_of_parameters);
}
здесь
- InterfaceName – имя функционального интерфейса;
- MethodName – имя метода, который определяет назначение интерфейса;
- return_type – тип, который возвращает метод MethodName;
- list_of_parameters – список параметров метода MethodName.
Если в интерфейсе объявляется два и более абстрактных метода, то этот интерфейс не считается функциональным интерфейсом.
Пример 1. Объявляется функциональный интерфейс, который определяет абстрактный метод, не получающий параметров и возвращающий целое число
// Интерфейс, который определяет метод без параметров, который возвращает целое число interface INumber { int GetNumber(); }
Код лямбда-выражения, реализующий данный интерфейс, может быть, например, следующим:
// Лямбда-выражение, реализующее интерфейс INumber
INumber lambdaNumber = () -> 255;
После формирования лямбда-выражения можно вызвать метод GetNumber() интерфейса INumber()
// Использование лямбда-выражения в виде метода GetNumber() int number = lambdaNumber.GetNumber(); // number = 255 System.out.println("number = " + number);
Пример 2. Объявляется функциональный интерфейс, который определяет метод, получает 3 числа типа double и возвращает значение типа double
// Интерфейс, определяющий абстрактный метод, который получает 3 параметра // типа double и возвращает значение типа double. interface ICalculation { double CalcMethod(double a, double b, double c); }
Для такой сигнатуры метода можно реализовывать лямбда-выражения, которые выполняют различные операции над тремя числами, например:
- вычисляют сумму трех чисел;
- вычисляют максимальное (минимальное) значение среди трех чисел и т.д.
Ниже приведено построение и использование лямбда-выражения, вычисляющего сумму трех чисел
// Построение лямбда-выражения для интерфейса ICalculation // 1. Формирование метода CalcMethod(), который вычисляет // сумму 3-х чисел типа double с помощью лямбда-выражения ICalculation ICalc = (a, b, c) -> a+b+c; // 2. Вычислить сумму целых чисел 1+2+3 int sum = (int)ICalc.CalcMethod(1, 2, 3); System.out.println("sum = " + sum);
Как видно из вышеприведенного кода, допускается использование целочисленных типов в методе, который оперирует типом double.
⇑
4. Последовательность шагов при построении лямбда-выражений
Для того, чтобы в программе использовать лямбда-выражения нужно выполнить следующие шаги:
- Объявить функциональный интерфейс I, содержащий только один абстрактный метод M. Сигнатура параметров метода M и тип возвращаемого результата должны соответствовать решаемой задаче.
- Объявить ссылку Ref на функциональный интерфейс I в методе, где нужно использовать лямбда-выражение.
- Сформировать код лямбда-выражения согласно синтаксису и присвоить этот код ссылке Ref на функциональный интерфейс. Количество параметров в лямбда-выражении должно совпадать с количеством параметров, которые объявлены в методе M функционального интерфейса I.
- С помощью ссылки Ref вызвать метод M. Вызов выглядит следующим образом:
Ref.M(parameters)
здесь parameters – список параметров метода M.
⇑
5. Решение задач на лямбда-выражения
5.1. Лямбда-выражение, которое обрабатывает три числа
Условие задачи. Создать и вызвать лямбда-выражение, которое вычисляет:
- сумму трех чисел;
- максимальное значение из трех чисел.
Задачу реализовать с использованием функционального интерфейса.
Решение. Текст программы следующий.
package TrainLambda; // Объявить функциональный интерфейс interface MyNumber { // Метод, получающий три параметра типа double // и возвращающий результат типа double double GetValue(double a, double b, double c); } public class TrainLambda01 { public static void main(String[] args) { // Использование функционального интерфейса MyNumber // 1. Объявить ссылку на функциональный интерфейс MyNumber mn; // 2. Присвоить ссылке mn лямбда-выражение, здесь создается // экземпляр класса с реализацией метода GetValue(). // Ссылка mn есть ссылкой на этот экземпляр класса. mn=(a,b,c)->a+b+c; // одиночное лямбда-выражение // 3. Вызвать метод GetValue() и вывести результат double res = mn.GetValue(5, 7.5, 3.8); System.out.println("res = " + res); // -------------------------------- // 4. Вычисление максимума из трех чисел // Сформировать новое блочное лямбда-выражение mn=(a,b,c) -> { double max=a; if (max<b) max=b; if (max<c) max=c; return max; }; // Вызов метода GetValue() и вывод результата double max = mn.GetValue(5.3, 8.3, 4.2); System.out.println("max = " + max); } }
Результат выполнение программы
res = 16.3
⇑
5.2. Лямбда-выражение, которое используется в параметре метода для обработки массива чисел
Реализовать и продемонстрировать метод, который находит сумму парных и непарных элементов целочисленного массива. Метод должен получать параметром лямбда-выражение, которое определяет четность элемента и использовать его для вычислений. Продемонстрировать работу метода для лямбда-выражений, которые:
- определяют, является ли элемент массива парным;
- определяют, является ли элемент массива непарным.
package TrainLambda; // Функциональный интерфейс, который предназначен для проверки числа на четность. // Этот интерфейс содержит только 1 метод, который получает целое число и // возвращает результат логического типа. interface Odd { // Абстрактный метод проверки на четность числа типа int boolean IsOdd(int d); } // Класс, содержащий метод, находящий сумму элементов массива class SumArray { // Метод, вычисляющий сумму элементов массива. // При вычислении суммы учитывается результат возврата из лямбда-выражения. // Метод получает 2 параметра: // - A - суммируемый массив; // - refLambda - ссылка на функциональный интерфейс Odd. public int Sum(int[] A, Odd refLambda) { int sum=0; for (int i=0; i<A.length; i++) if (refLambda.IsOdd(A[i])) // четный ли элемент массива? sum+=A[i]; return sum; } } public class TrainLambda01 { public static void main(String[] args) { // Передача лямбда-выражения в метод как аргумента. // 1. Объявить ссылку на интерфейс Odd Odd ref1; // 2. Присвоить ссылке ref1 лямбда-выражение ref1 = (d) -> { // код проверки на четность if (d%2==0) return true; else return false; }; // 3. Продемонстрировать вызов метода Sum класса SumArray и передачу ему лямбда-выражения // 3.1. Создать экземпляр класса SumArray SumArray obj1 = new SumArray(); // 3.2. Объявить массив целых чисел int[] A = { 5, 3, 8, 10, 2, 1, 2, 3 }; // 3.3. Вызвать метод вычисления суммы элементов массива // с лямбда-выражением, которое выбирает только парные элементы int sum = obj1.Sum(A, ref1); System.out.println("sum = " + sum); // 3.4. Передача в метод лямбда-выражения для непарных элементов sum = obj1.Sum(A, (Odd)((d) -> { if (d%2!=0) return true; else return false; })); System.out.println("sum = " + sum); } }
Результат выполнения программы
sum = 22 sum = 12
В вышеприведенном коде можно использовать ссылку на метод по образцу:
имя_экземпляра::имя_метода
В программе передача метода IsOdd() в метод Sum() будет выглядеть следующим образом:
// также работает - передаеться ссылка на метод экземпляра int sum = obj1.Sum(A, ref1::IsOdd);
⇑
5.3. Лямбда-выражение, использующее шаблон (обобщение)
Создать и вызвать лямбда-выражение используя шаблон (обобщение). Лямбда-выражение должно находить максимум из трех цифр.
package TrainLambda; // Обобщенный (шаблонный) функциональный интерфейс. // Содержит метод, получающий 3 числа типа T и // возвращающий значение типа T. interface Max3<T> { T Max(T a, T b, T c); } public class TrainLambda02 { public static void main(String[] args) { // Использовение интерфейса Max3<T> // 1. Объявить ссылку на интерфейс Max3<T> Max3<Integer> refInt; // 2. Создать лямбда-выражение с привязкой к типу int refInt = (a, b, c) -> { Integer max = a; if (max<b) max=b; if (max<c) max=c; return max; }; // 3. Вызвать лямбда выражение для трех целых чисел int res = refInt.Max(5, 7, 3); // res = 7 System.out.println("res = " + res); // 4. Создать лямбда-выражение с привязкой к типу double Max3<Double> refDouble; refDouble = (a, b, c) -> { Double max = a; if (max<b) max=b; if (max<c) max=c; return max; }; // 5. Вызвать лямбда-выражение с привязкой к типу double double resMax = refDouble.Max(3.88, 2.55, 4.11); System.out.println("resMax = " + resMax); } }
⇑
5.4. Решение квадратного уравнения с помощью лямбда-выражения
В задаче демонстрируется использование лямбда-выражения для решения квадратного уравнения. Программа содержит следующие составляющие:
- функциональный интерфейс Equation. В интерфейсе объявляется один метод, получающий три параметра типа double. Эти параметры являются коэффициентами a, b, c квадратного уравнения. Метод возвращает значение корней уравнения в виде экземпляра класса Solution. Если уравнение не имеет решения, то метод возвращает null;
- класс Solution — содержит два поля x1, x2 типа double, которые являются корнями квадратного уравнения;
- класс TrainLambda02, содержащий функцию main(). В этой функции выполняется построение и использование лямбда-выражения для решения квадратного уравнения для значений a = 2, b = 8, c = 4.
Текст программы следующий:
// Функциональный интерфейс для решения квадратного уравнения. interface Equation { // Метод, получающий параметры - коэффициенты уравнения a*x^2 + b*x + c = 0. // Если уравнение не имеет решения, то метод возвращает null. // В другом случае, метод возвращает экземпляр класса Solution // с корнями уравнения x1, x2. Solution CalcEquation(double a, double b, double c); } // Результат решения квадратного уравнения class Solution { double x1; double x2; } public class TrainLambda02 { public static void main(String[] args) { // Лямбда-выражение, решающее квадратное уравнение. // Результат возвращается в экземпляре класса Solution. // 1. Объявить ссылку на функциональный интерфейс Equation eq; // 2. Присвоить ссылке лямбда-выражение, решающее // квадратное уравнение eq = (double a, double b, double c) -> { double d = b*b - 4*a*c; // дискриминант if (d>=0) { // Найти корни Solution roots = new Solution(); roots.x1 = (-b-Math.sqrt(d))/(2*a); roots.x2 = (-b+Math.sqrt(d))/(2*a); return roots; } else return null; }; // 3. Решить квадратное уравнение 2*x^2 - 8*x + 4 = 0 Solution roots = eq.CalcEquation(2, -8, 4); if (roots==null) System.out.println("The solution has no roots."); else { System.out.println("x1 = " + roots.x1); System.out.println("x2 = " + roots.x2); } } }
Результат выполнения программы
x1 = 0.5857864376269049 x2 = 3.414213562373095
⇑
Связанные темы
- Лямбда-выражения для обобщенных функциональных интерфейсов
- Передача лямбда-выражения в метод в качестве параметра. Примеры
- Генерирование исключений в лямбда-выражениях. Примеры
- Доступ к элементам класса в лямбда-выражениях. Захват переменных в лямбда-выражениях. Примеры
⇑