Чи корисно використовувати вирази лямбда, коли це можливо в java?


52

Нещодавно я освоїв вираз Lambda, який був представлений в java 8. Я вважаю, що коли я використовую функціональний інтерфейс, я прагну завжди використовувати вираз Lambda замість створення класу, який реалізує функціональний інтерфейс.

Це вважається хорошою практикою? Або є їх ситуації, коли використання Lambda для функціонального інтерфейсу не підходить?


122
На питання "Використовувати техніку X, коли це можливо, хороша практика", єдино правильна відповідь "ні, використовуйте техніку X не завжди, коли можливо, але коли це можливо ."
Док Браун

Вибір того, коли використовувати лямбда (який є анонімним) та якийсь інший вид функціональної реалізації (наприклад, посилання на метод), є справді цікавим питанням. Я мав нагоду познайомитися з Джошем Блохом пару днів тому, і це був один із пунктів, про який він говорив. З того, що він сказав, я розумію, що планує додати новий елемент до наступного видання « Ефективна Java», що займається саме цим питанням.
Даніель Приден

@DocBrown Це має бути відповідь, а не коментар.
gnasher729

1
@ gnasher729: точно ні.
Док Браун

Відповіді:


116

Існує ряд критеріїв, які повинні змусити вас не використовувати лямбда:

  • Розмір Чим більше лямбда отримує, тим складніше дотримуватися логіку, що її оточує.
  • Повторення Краще створити іменовану функцію для повторної логіки, хоча нормально повторювати дуже прості лямбди, які розкидаються на частини.
  • Іменування Якщо ви можете думати про великий семантичному імені, ви повинні використовувати це замість того, щоб , як він додає багато ясності в код. Я не кажу таких імен priceIsOver100. x -> x.price > 100так само зрозуміло, як це ім'я. Я маю на увазі такі імена, isEligibleVoterякі замінюють довгий список умов.
  • Гніздування вкладених лямбдів насправді дуже важко читати.

Не переходьте за борт. Пам'ятайте, що програмне забезпечення легко змінюється. Коли ви сумніваєтесь, пишіть це обома способами та подивіться, що легше читати.


1
Також важливо враховувати кількість даних, що підлягають обробці через лямбда, оскільки лямбда є досить неефективними при невеликих обсягах обробки даних. Вони справді світяться паралелізацією великих наборів даних.
CraigR8806

1
@ CraigR8806 Я не думаю, що ефективність тут викликає занепокоєння. Використання анонімної функції або створення класу, що розширює функціональний інтерфейс, має бути однаковою для продуктивності. Міркування щодо ефективності виникають, коли ви говорите про використання потоків / функцій вищого порядку api над циклом імперативного стилю, але в цьому питанні ми використовуємо функціональні інтерфейси в обох випадках просто з різним синтаксисом.
puhlen

17
"Хоча добре повторювати дуже прості лямбди, які розкидаються на частини" Тільки якщо вони не обов'язково змінюються разом. Якщо всі вони повинні мати однакову логіку, щоб ваш код був правильним, ви повинні зробити їх усі фактично однаковим методом, так що зміни потрібно робити лише в одному місці.
jpmc26

Загалом це справді хороша відповідь. Згадування про використання посилання на метод як альтернативу лямбда може привернути лише дірку, яку я бачу в цій відповіді.
Морген

3
Коли ви сумніваєтесь, пишіть це обома способами і запитайте когось іншого, хто підтримує код, який легше читати.
corsiKa

14

Це залежить. Щоразу, коли ви виявите, що використовуєте одну і ту ж лямбда в різних місцях, вам слід розглянути можливість реалізації класу, який реалізує інтерфейс. Але якби ти використовував анонімний внутрішній клас, то я думаю, що лямбда набагато кращий.


6
Не забувайте про можливість використання посилання на метод! Oracle додав (і додає ще більше в Java 9) статичні методи та методи за замовчуванням всюди саме з цієї причини.
Йорг W Міттаг

13

Я підтримую відповідь Карла Білефельдта, але хочу надати коротке доповнення.

  • Налагодження деякої боротьби IDE із сферою дії всередині лямбда та боротьба за відображення змінних членів всередині контексту лямбда. Хоча, сподіваємось, ця ситуація зміниться вниз, але може бути прикро підтримувати чужий код, коли він засмічений лямбдами.

Безумовно, що стосується .NET. Це не змушує мене уникати лямбдів, обов'язково, але я, звичайно, не відчуваю жодної провини, якщо замість цього роблю щось інше.
користувач1172763

3
Якщо це так, ви використовуєте неправильну IDE. І IntelliJ, і Netbeans досить добре працюють в цій галузі.
Девід Фоерстер

1
@ user1172763 У Visual Studio, якщо ви хочете переглянути змінні учасників, я виявив, що ви можете переміщати стек викликів, поки не потрапите в контекст лямбда.
Зев Шпіц

6

Доступ до локальних змінних додаткової області

Прийнятий відповідь Карла Білефельдта є правильним. Я можу додати ще одне відмінність:

  • Область застосування

Код лямбда, вкладений всередині методу всередині класу, може отримати доступ до будь-яких ефективно- кінцевих змінних, знайдених у цьому методі та класі.

Створення класу, який реалізує функціональний інтерфейс, не дає вам такого прямого доступу до стану викличного коду.

Щоб процитувати підручник Java (моє наголос):

Як і локальні та анонімні класи, лямбда-вирази можуть фіксувати змінні; вони мають однаковий доступ до локальних змінних додаткової області . Однак, на відміну від локальних та анонімних класів, лямбда-вирази не мають жодних проблем із затіненням (див. Shadowing для отримання додаткової інформації). Лямбдаські вирази мають лексичний діапазон. Це означає, що вони не успадковують імен із супертипу або не запроваджують новий рівень масштабування. Декларації в лямбдаському виразі трактуються так само, як вони знаходяться в оточуючому середовищі.

Тож, хоча є витягнути довгий код і називати його корисними, ви повинні зважити це на простоту прямого доступу до стану методу та класу, що додає.

Побачити:


4

Це може бути вибиванням ніт, але до всіх інших відмінних моментів, викладених в інших відповідях, я додам:

Віддайте перевагу методик, коли це можливо. Порівняйте:

employees.stream()
         .map(Employee::getName)
         .forEach(System.out::println);

проти

employees.stream()
         .map(employee -> employee.getName())
         .forEach(employeeName -> System.out.println(employeeName));

Використання посилання на метод економить вам потребу в назві аргументів (лямбда) лямбда, які, як правило, зайві та / або призводять до ледачих імен, як eабо x.


0

Спираючись на відповідь Метта Макенрі, може існувати залежність між лямбда та посиланнями методів, де лямбда може бути переписана як серія посилань на метод. Наприклад:

employees.stream()
         .forEach(employeeName -> System.out.println(employee.getName()));

проти

employees.stream()
         .map(Employee::getName)
         .forEach(System.out::println);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.