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
⇑
Зв’язані теми
- Лямбда-вирази для узагальнених функціональних інтерфейсів. Приклади
- Передача лямбда-виразу в метод в якості параметру. Приклади
- Генерування виключень у лямбда-виразах. Приклади
- Доступ до елементів класу з лямбда-виразу. Захоплення змінних у лямбда-виразах. Приклади
⇑