Является ли использование лямбда-выражений, когда это возможно, в java хорошей практикой?

52

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

Это считается хорошей практикой? Или их ситуации, когда использование лямбды для функционального интерфейса не подходит?

Стальной подносок
источник
122
На вопрос «Использует ли техника X всякий раз, когда это возможно, хорошую практику», единственный правильный ответ - «Нет, используйте технику X не всегда, когда это возможно, но когда это целесообразно» .
Док Браун
Выбор того, когда использовать лямбду (которая является анонимной) по сравнению с каким-либо другим видом функциональной реализации (например, ссылкой на метод), является действительно интересным вопросом. У меня была возможность встретиться с Джошем Блохом пару дней назад, и это был один из моментов, о которых он говорил. Из того, что он сказал, я понимаю, что он планирует добавить новый пункт в следующую редакцию Effective Java, посвященный именно этому вопросу.
Даниэль Приден
@DocBrown Это должен быть ответ, а не комментарий.
gnasher729
1
@ gnasher729: определенно нет.
Док Браун

Ответы:

116

Существует ряд критериев, которые следует учитывать при использовании лямбды:

  • Размер Чем больше становится лямбда, тем сложнее ей следовать логике, окружающей ее.
  • Повторение Лучше создать именованную функцию для повторяющейся логики, хотя можно повторять очень простые лямбды, которые разбросаны.
  • Присвоение имен Если вы можете придумать хорошее семантическое имя, вы должны использовать его вместо этого, так как оно добавляет большую ясность вашему коду. Я не говорю такие имена, как priceIsOver100. x -> x.price > 100так же ясно, как это имя. Я имею в виду такие имена, isEligibleVoterкоторые заменяют длинный список условий.
  • Вложенность Вложенные лямбды действительно очень трудно читать.

Не уходи за борт. Помните, программное обеспечение легко меняется. Если сомневаетесь, напишите обоими способами и посмотрите, что легче читать.

Карл Билефельдт
источник
1
Также важно учитывать объем данных, которые должны обрабатываться через лямбду, поскольку лямбды довольно неэффективны при небольших объемах обработки данных. Они действительно сияют при распараллеливании больших наборов данных.
CraigR8806
1
@ CraigR8806 Я не думаю, что производительность является проблемой здесь. Использование анонимной функции или создание класса, расширяющего функциональный интерфейс, должно быть одинаково с точки зрения производительности. Вопросы производительности возникают, когда вы говорите об использовании API-интерфейсов streams / high-order-functions через цикл императивного стиля, но в этом вопросе мы используем функциональные интерфейсы в обоих случаях только с разным синтаксисом.
Пухлен
17
«хотя можно повторять очень простые лямбды, которые разбросаны друг от друга», только если они не обязательно изменяются вместе. Если они должны быть одинаковой логики, чтобы ваш код был корректным, вы должны сделать их фактически одним и тем же методом, чтобы изменения нужно было вносить только в одном месте.
jpmc26
В целом это действительно хороший ответ. Упоминание об использовании ссылки на метод в качестве альтернативы лямбда-патчу исправит только дыру, которую я вижу в этом ответе.
Морген
3
Если сомневаетесь, пишите обоими способами и спросите кого-то еще, кто поддерживает код, который легче читать.
CorsiKa
14

По-разному. Всякий раз, когда вы обнаруживаете, что используете одну и ту же лямбду в разных местах, вам следует подумать о реализации класса, реализующего интерфейс. Но если бы вы использовали анонимный внутренний класс, иначе я думаю, что лямбда гораздо лучше.

Tohnmeister
источник
6
Не забывайте о возможности использования ссылки на метод! Именно по этой причине Oracle добавил (и добавляет еще больше в Java 9) статические методы и методы по умолчанию.
Йорг Миттаг
13

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

  • Отладка Некоторая борьба IDE с областью действия внутри лямбды и борьба за отображение переменных-членов в контексте лямбды. Хотя мы надеемся, что эта ситуация изменится в дальнейшем, это может быть раздражающим, чтобы поддерживать чужой код, когда он завален лямбдами.
Jolleyboy
источник
Определенно верно для .NET. Не обязательно заставляет меня избегать лямбды, но я, конечно, не чувствую никакой вины, если я делаю что-то еще.
user1172763
3
Если это так, вы используете не ту IDE. И IntelliJ, и Netbeans хорошо справляются с этой задачей.
Дэвид Фёрстер
1
@ user1172763 В Visual Studio, если вы хотите просматривать переменные-члены, я обнаружил, что вы можете перемещаться вверх по стеку вызовов, пока не попадете в контекст лямбды.
Зев Шпиц
6

Доступ к локальным переменным вмещающей области

Принятый ответ Карла Билефельдта правильный. Я могу добавить еще одно различие:

  • Объем

Лямбда-код, вложенный в метод внутри класса, может получить доступ к любым эффективно- конечным переменным, найденным в этом методе и классе.

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

Чтобы процитировать Java Tutorial (курсив мой):

Как и локальные и анонимные классы, лямбда-выражения могут захватывать переменные; они имеют одинаковый доступ к локальным переменным окружения . Однако, в отличие от локальных и анонимных классов, у лямбда-выражений нет проблем с теневым копированием (для получения дополнительной информации см. Теневое копирование). Лямбда-выражения лексически ограничены. Это означает, что они не наследуют никаких имен от супертипа или вводят новый уровень видимости. Объявления в лямбда-выражении интерпретируются так же, как и во внешней среде.

Таким образом, несмотря на преимущества извлечения длинного кода и присвоения ему имен, вы должны сравнить это с простотой прямого доступа к состоянию включающего метода и класса.

Видеть:

Базилик Бурк
источник
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);
LaFayette
источник