Лямбда представляє набір інструкцій, які можна виділити в окрему змінну та багаторазово викликати в різних місцях програми.
Основу лямбда-виразу становить лямбда-оператор, який представляє стрілку ->.
Синтаксис лямбда-виразу має вигляд:
параметри -> {тіло функції}.
Типи параметрів можна опускати.
Приклад:
p -> return p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25
Функція як параметр
У багатьох мовах функцію можна передавати як параметр.
• Динамічне визначення типу:
JavaScript, Lisp, Sceme, …
• Сувора типізація:
Ruby, Scala, …
• Функціональний підхід дає змогу писати коротший та результативний код.
JavaScript:
var testStrings = [“one”, “two”, “three”, “four”];
testString.sort(function(s1, s2) {
return (s1.length – s2.length) ;
} );
Переваги лямбда-виразів
Основні переваги:
• лаконічний і виразний код.
Переваги лямбда-виразів
Додаткові переваги:
• Функціональний підхід: багато класів завдань розв'язуються простіше, код стає легким для читання, що спрощує його супровід.
• Функціональний підхід: багато класів завдань розв'язуються простіше, код стає легким для читання, що спрощує його супровід.
Лямбда-вираз не виконується само собою, а утворює реалізацію методу, визначеного у функціональному інтерфейсі або SAM-інтерфейсом (Single Abstract Method).
Водночас важливо, що функціональний інтерфейс має містити лише один метод без реалізації.
Сортування рядків за довжиною
String[] testStrings = {"one", "two", "three", "four"};
Було:
Arrays.sort(testStrings, new Comparator() {
public int compare(String s1, String s2) {
return(s1.length() - s2.length());
}
});
Стало:
Arrays.sort(testStrings, (s1, s2) -> s1.length() – s2.length());
Правила лямбда:
Виведення типів:
• У списку аргументів можна нехтувати вказівкою типів.
• Загальний вигляд λ-виразу. (Тип1 var1, тип2 var2 ...) -> { тіло методу }
• λ-вираз із виведенням типів. (var1, var2 ...) -> { тіло методу }
Дужки:
• Якщо метод залежить від одного аргументу, можна опустити дужки.
• У такому разі тип аргументу не вказується.
• Було: (varName) -> someResult()
• Стало: varName -> someResult()
Області видимості змінних
• Лямбда-вирази використовують статичні області дії змінних.
Висновки:
• Ключове слово this посилається на зовнішній клас, а не на анонімний (той, в який перетворюється лямбда-вираз).
• Немає змінної OuterClass.this.
• Поки лямбда всередині вкладеного класу.
• Лямбда не може створювати нові змінні з такими ж іменами як метод, що викликав лямбда.
• Лямбда може посилатися (але не змінювати) локальні змінні з навколишнього коду.
• Лямбда може звертатися (і змінювати) змінні екземпляри навколишнього класу.
Області видимості змінних
Помилка: повторне використання імені змінної
double x = 1.2;
someMethod(x -> doSomethingWith(x));
Помилка: повторне використання імені змінної
double x = 1.2;
someMethod(y -> { double x = 3.4; ... });
Помилка: лямбда змінює локальну змінну
double x = 1.2;
someMethod(y -> x = 3.4);
Зміна змінної екземпляра private
double x = 1.2;
public void foo() { someMethod(y -> x = 3.4);}
Ім'я змінної в лямбда збігається з ім'ям змінної екземпляра
private double x = 1.2; public void bar() {
someMethod(x -> x + this.x);
}
Інтерфейси
• java.util.function містить багато інтерфейсів різного призначення.
• Наприклад, прості інтерфейси:
• IntPredicate, LongUnaryOperator, DoubleBinaryOperator.
• А також узагальнені:
• Predicate<T> — аргумент T, повертає boolean.
• Function<T,R> — аргумент T, повертає R.
• Consumer<T> — аргумент T, нічого не повертає (void).
• Supplier<T> — немає аргументів, повертає T.
• BinaryOperator<T> — 2 аргументи T і T, повертає T.
Анотація
@FunctionalInterface
• Відстежує помилки під час компіляції.
• Якщо розробник додасть другий абстрактний метод до інтерфейсу, інтерфейс не буде скомпільований.
• Описує суть інтерфейсу.
• Повідомляє іншим розробникам, що цей інтерфейс використовуватиметься з лямбда-виразами.
• Анотація не обов'язкова.
Використання значень
• Лямбда-вирази можуть посилатися на змінні, які не оголошені як final (але значення таким змінним можна присвоїти лише один раз).
• Такі змінні називаються ефективно фінальними (їх можна коректно оголосити як final).
• Також можна посилатися на змінні змінні екземпляра: «this» у лямбда-виразі посилається на головний клас (не вкладений, який створюється для лямбда-виразу).
Посилання на методи
Посилання на методи можна використовувати у лямбда-виразах.
Якщо є метод, сигнатура якого збігається з сигнатурою абстрактного методу функціонального інтерфейсу, можна використовувати посилання:
Методи, що повертають лямбда
• Метод може повертати лямбда-вираз (насправді об'єкт, який реалізує функціональний інтерфейс).
• В інтерфейсах Predicate, Function, Consumer є інтегровані методи, що повертають лямбда-вираз.
Predicate isRich = e -> e.getSalary() > 200000;
Predicate isEarly = e -> e.getEmployeeId() <= 10;
allMatches(employees,isRich.and(isEarly))
Методи інтерфейсу Predicate
• and як аргумент приймає Predicate, повертає Predicate, у якому метод test повертає true, якщо обидва вихідні об'єкти Predicate повертають true для заданих аргументів. Метод за замовчуванням.
• or як аргумент приймає Predicate, повертає Predicate, в якому метод test повертає true, якщо хоча б один із вихідних об'єктів Predicate повертає true для заданих аргументів. Метод за замовчуванням.
• negate – метод без аргументів. Повертає Predicate, в якому метод test повертає заперечення значення, яке повертається, вихідного об'єкта Predicate. Метод за замовчуванням.
• isEqual приймає як аргумент Object, повертає Predicate, у якому метод test повертає true, якщо об'єкт Predicate еквівалентний аргументу Object. Статичний метод.
Приклад Predicate
Predicate isRich = e -> e.getSalary() > 200000;
Predicate isEarly = e -> e.getEmployeeId() <= 10;
System.out.printf("Заможні: %s.%n", allMatches(employees, isRich));
System.out.printf("Найняті раніше: %s.%n", allMatches(employees, isEarly));
System.out.printf("Найняті раніше: %s.%n", allMatches(employees, isEarly));
System.out.printf("Заможні або найняті раніше: %s.%n", allMatches(employees, isRich.or(isEarly)));
System.out.printf("Незаможні: %s.%n", allMatches(employees, isRich.negate()));
Методи інтерфейсу Function
• compose – f1.compose(f2) означає спочатку виконати f2 і потім передати результат f1. Метод за замовчуванням.
• andThen – f1.andThen (f2) означає спочатку виконати f1 і потім передати результат f2. Тобто f2.andThen(f1) – це те саме, що f1.compose(f2). Метод за замовчуванням.
• identity створює функцію, у якої метод apply повертає аргумент без змін. Статичний метод.
Методи інтерфейсу Consumer
• and як аргумент приймає Predicate, повертає Predicate, у якому метод test повертає true, якщо обидва вихідні об'єкти Predicate повертають true для заданих аргументів. Метод за замовчуванням.
• or як аргумент приймає Predicate, повертає Predicate, в якому метод test повертає true, якщо хоча б один із вихідних об'єктів Predicate повертає true для заданих аргументів. Метод за замовчуванням.
• negate – метод без аргументів. Повертає Predicate, в якому метод test повертає заперечення значення, яке повертається, вихідного об'єкта Predicate. Метод за замовчуванням.
• isEqual приймає як аргумент Object, повертає Predicate, у якому метод test повертає true, якщо об'єкт Predicate еквівалентний аргументу Object. Статичний метод.
• andThen – f1.andThen(f2) повертає Consumer, який передає аргумент f1 (тобто його методу accept), після цього передає аргумент f2. Метод за замовчуванням.
• Різниця методів у Consumer і Function:
• У методі andThen Consumer аргумент передається методу accept до f1, а потім той же аргумент передається в f2.
• У методі andThen з Function аргумент передається методу apply з f1, а потім отриманий результат передається в метод apply з f2.
Інтерфейс Supplier
• T get() дає змогу задати «функцію» без аргументів і повертає T. та виконує деякий побічний ефект.
• Синтаксис
Supplier maker1 = Employee::new;
Supplier maker2 = () -> randomEmployee();
Employee e1 = maker1.get();
Employee e2 = maker2.get();
Інтерфейс BinaryOperation