Lambda-вирази на прикладах

Lambda-вирази на прикладах

Java спочатку повністю об'єктно-орієнтована мова. За винятком примітивних типів, все в Java - це об'єкти. Навіть масиви є об'єктами. Примірники кожного класу - об'єкти. Не існує жодної можливості визначити окремо (поза класом - прим. Перекл.) Якусь функцію. І немає ніякої можливості передати метод як аргумент або повернути тіло методу як результат іншого методу. Все так. Але так було до Java 8.

З часів старого доброго Swing, треба було писати анонімні класи, коли потрібно було передати якусь функціональність в який-небудь метод. Наприклад, так виглядало додавання обробника подій:

Тут ми хочемо додати деякий код в слухач подій від миші. Ми визначили анонімний клас MouseAdapter і відразу створили об'єкт з нього. Таким способом ми передали додаткову функціональність в метод addMouseListener.

Коротше кажучи, не так-то просто передати простий метод (функціональність) в Java через аргументи. Це обмеження змусило розробників Java 8 додати в специфікацію мови таку можливість як Lambda-вирази.

Навіщо Яві Lambda-вирази?

У своєму саркастично і забавний блозі, Стів ІЕГ (Steve Yegge) описує наскільки світ Java строго зав'язаний на іменники (сутності, об'єкти - прим. Перекл.). Якщо ви не Новомосковсклі його блог, рекомендую. Він забавно і цікаво описує точну причину того, чому в Java додали Lambda-вирази.

Lambda-вирази привносять в Java функціональне ланка, якого так давно не вистачало. Lambda-вирази вносять в мову функціональність на рівні з об'єктами. Хоча це і не на 100% вірно, можна бачити, що Lambda-вирази не будучи замиканнями надають схожі можливості. У функціональному мовою lambda-вирази - це функції; але в Java, lambda-вирази - представляються об'єктами, і повинні бути пов'язані з конкретним об'єктним типом, який називається функціональний інтерфейс. Далі ми розглянемо, що він із себе представляє.

У статті Маріо Фаско (Mario Fusco) "Навіщо в Java потрібні Lambda-вирази" ( "Why we need Lambda Expression in Java") докладно описано, навіщо всім сучасним мовам потрібні можливості замикань.

Введення в Lambda-вирази

Lambda-вирази в Java зазвичай мають наступний синтаксис (аргументи) -> (тіло). наприклад:

Далі йде кілька прикладів справжніх Lambda-виразів:

Структура Lambda-виразів


Давайте вивчимо структуру lambda-виразів:

• Lambda-вирази можуть мати від 0 і більше вхідних параметрів.
• Тип параметрів можна вказувати явно або може бути отриманий з контексту. Наприклад (int a) можна записати і так (a)
• Параметри полягають в круглі дужки і розділяються комами. Наприклад (a, b) або (int a, int b) або (String a, int b, float c)
• Якщо параметрів немає, то потрібно використовувати порожні круглі дужки. Наприклад () -> 42
• Коли параметр один, якщо тип не вказується явно, дужки можна опустити. Приклад: a -> return a * a
• Тіло Lambda-вирази може містити від 0 і більше виразів.
• Якщо тіло складається з одного оператора, його можна не укладати в фігурні дужки, а повертається значення можна вказувати без ключового слова return.
• В іншому випадку фігурні дужки обов'язкові (блок коду), а в кінці треба вказувати значення, що повертається з використанням ключового слова return (в іншому випадку типом значення, що повертається буде void).

Що таке функціональний інтерфейс

java.lang.Runnable - це приклад функціонального інтерфейсу. У ньому оголошений тільки один метод void run (). Також є інтерфейс ActionListener - теж функціональний. Раніше нам доводилося використовувати анонімні класи для створення об'єктів, що реалізують функціональний інтерфейс. З Lambda-виразами, все стало простіше.
Кожне lambda-вираз може бути неявно прив'язане до якогось функціональному інтерфейсу. Наприклад, можна створити посилання на Runnable інтерфейс, як показано в наступному прикладі:

Подібне перетворення завжди здійснюється неявно, коли ми не вказуємо функціональний інтерфейс:

В наведеному вище прикладі, компілятор автоматично створює lambda-вираз як реалізацію Runnable інтерфейсу з конструктора класу Thread: public Thread (Runnable r) <> .

Наведу кілька прикладів lambda-виразів і відповідних функціональних інтерфейсів:

Анотація @FunctionalInterface. додана в Java 8 згідно Java Language Specification, перевіряє чи є оголошений інтерфейс функціональним. Крім того, в Java 8 включений ряд готових функціональних інтерфейсів для використання з Lambda-виразами. @FunctionalInterface видасть помилку компіляції, якщо оголошений інтерфейс не буде функціональним.

Далі наводиться приклад визначення функціонального інтерфейсу:

Як випливає з визначення, функціональний інтерфейс може мати тільки один абстрактний метод. Якщо спробувати додати ще один абстрактний метод, то вилізе помилка компіляції. приклад:

Після визначення функціонального інтерфейсу, ми можемо його використовувати і отримувати всі переваги Lambda-виразів. приклад:

Тут ми визначили свій власний функціональний інтерфейс і скористалися lambda-виразом. Метод execute () він може приймати lambda-вирази як аргумент.

Приклади Lambda-виразів


Кращий спосіб вникнути в Lambda-вирази - це розглянути кілька прикладів:
Потік Thread можна проинициализировать двома способами:


Управління подіями в Java 8 також можна здійснювати через Lambda-вирази. Далі представлені два способи додавання обробника події ActionListener в компонент для користувача інтерфейсу:


Простий приклад виведення всіх елементів заданого масиву. Зауважте, що є більш одного способу використання lambda-вирази. Нижче ми створюємо lambda-вираз звичайним способом, використовуючи синтаксис стрілки, а також ми використовуємо оператор подвійного двокрапки (: :), який в Java 8 конвертує звичайний метод в lambda-вираз:

У наступному прикладі ми використовуємо функціональний інтерфейс Predicate для створення тесту і друку елементів, які пройшли цей тест. Таким способом ви можете поміщати логіку в lambda-вирази і робити що-небудь на її основі.

Повороживши над Lambda-виразами можна вивести квадрат кожного елемента списку. Зауважте, що ми використовуємо метод stream (), щоб перетворити звичайний список в потік. Java 8 надає шикарний клас Stream (java.util.stream.Stream). Він містить тонни корисних методів, з якими можна використовувати lambda-вирази. Ми передаємо lambda-вираз x -> x * x в метод map (), який застосовує його до всіх елементів в потоці. Після чого ми використовуємо forEach для друку всіх елементів списку.


Дан список, потрібно вивести суму квадратів всіх елемента списку. Lambda-вирази дозволяє досягти цього написанням всього одного рядка коду. У цьому прикладі застосований метод згортки (редукції) reduce (). Ми використовуємо метод map () для зведення в квадрат кожного елемента, а потім застосовуємо метод reduce () для згортки всіх елементів в одне число.

Відмінність Lambda-виразів від анонімних класів


Головна відмінність полягає в використанні ключового слова this. Для анонімних класів ключове слово 'this' позначає об'єкт анонімного класу, в той час як в lambda-вираженні 'this' позначає об'єкт класу, в якому lambda-вираз використовується.

Інша їхня відмінність полягає в способі компіляції. Java компілює lambda-вирази з перетворенням їх в private-методи класу. При цьому використовується інструкція invokedynamic. з'явилася в Java 7 для динамічної прив'язки методу. Тал Вайс (Tal Weiss) описав в своєму блозі як Java компілює lambda-вирази в байт-код

висновок

Незважаючи на певні плюси функціонального підходу, було завжди очевидно, що такі мови,
як Haskell, занадто кардинально змінює парадигму програмування, щоб прижитися на ринку. У той же час, деякі принципи цього напрямку заслуговують на особливу увагу. Дуже приємно спостерігати, як не революційним, а еволюційним шляхом, особливості функціональних мов потихеньку проникають в такі, що вже стали стандартами, язики поділені, немов C ++ і Java.