¿Es una buena práctica usar expresiones Lambda siempre que sea posible?

52

Recientemente he dominado la expresión Lambda que se introdujo en Java 8. Encuentro que cada vez que uso una interfaz funcional, tiendo a usar siempre una expresión Lambda en lugar de crear una clase que implemente la interfaz funcional.

¿Se considera esto una buena práctica? ¿O son situaciones en las que no es apropiado usar un Lambda para una interfaz funcional?

Puntera de acero
fuente
122
A la pregunta " Usar la técnica X siempre que sea posible es una buena práctica", la única respuesta correcta es "no, usar la técnica X no siempre que sea posible, sino siempre que sea razonable" .
Doc Brown
La elección de cuándo usar una lambda (que es anónima) frente a algún otro tipo de implementación funcional (por ejemplo, una referencia de método) es una pregunta realmente interesante. Tuve la oportunidad de conocer a Josh Bloch hace un par de días, y este fue uno de los puntos de los que habló. Por lo que dijo, entiendo que planea agregar un nuevo elemento a la próxima edición de Effective Java que trata esta pregunta exacta.
Daniel Pryden
@DocBrown Eso debería ser una respuesta, no un comentario.
gnasher729
1
@ gnasher729: definitivamente no.
Doc Brown

Respuestas:

116

Hay una serie de criterios que deberían hacerle considerar no usar una lambda:

  • Tamaño Cuanto más grande se hace un lambda, más difícil le resulta seguir la lógica que lo rodea.
  • Repetición Es mejor crear una función con nombre para lógica repetida, aunque está bien repetir lambdas muy simples que están separadas.
  • Nomenclatura Si puede pensar en un gran nombre semántico, debe usarlo en su lugar, ya que agrega mucha claridad a su código. No estoy hablando nombres como priceIsOver100. x -> x.price > 100es tan claro como ese nombre. Me refiero a nombres como isEligibleVoterese que reemplazan una larga lista de condiciones.
  • Anidamiento Las lambdas anidadas son realmente difíciles de leer.

No te vayas por la borda. Recuerde, el software se cambia fácilmente. En caso de duda, escríbalo en ambos sentidos y vea cuál es más fácil de leer.

Karl Bielefeldt
fuente
1
También es importante tener en cuenta la cantidad de datos que se procesarán a través de la lambda, ya que las lambdas son bastante ineficientes con pequeñas cantidades de procesamiento de datos. Realmente brillan en la paralelización de grandes conjuntos de datos.
CraigR8806
1
@ CraigR8806 No creo que el rendimiento sea una preocupación aquí. El uso de una función anónima o la creación de una clase que amplíe la interfaz funcional debería ser el mismo rendimiento inteligente. Las consideraciones de rendimiento se presentan cuando se habla de utilizar la API de secuencias / funciones de orden superior en un bucle de estilo imperativo, pero en esta pregunta estamos utilizando interfaces funcionales en ambos casos con una sintaxis diferente.
puhlen
17
"aunque está bien repetir lambdas muy simples que están separadas" Solo si no necesariamente cambian juntas. Si todos deben ser la misma lógica para que su código sea correcto, debe hacer que todos sean realmente el mismo método para que los cambios solo deban realizarse en un solo lugar.
jpmc26
En general, esta es una muy buena respuesta. Mencionar el uso de una referencia de método como alternativa a una lambda se aplicaría solo al agujero que veo en esta respuesta.
Morgen
3
En caso de duda, escríbelo en ambos sentidos y pregúntale a alguien que mantenga el código que sea más fácil de leer.
corsiKa
14

Depende. Siempre que se encuentre utilizando el mismo lambda en diferentes lugares, debería considerar implementar una clase que implemente la interfaz. Pero si hubieras usado una clase interna anónima, de lo contrario, creo que una lambda es mucho mejor.

Tohnmeister
fuente
66
¡No olvides la posibilidad de utilizar una referencia de método! Oracle ha agregado (y está agregando aún más en Java 9) métodos estáticos y métodos predeterminados en todo el lugar por exactamente esa razón.
Jörg W Mittag
13

Apoyo la respuesta de Karl Bielefeldt, pero quiero proporcionar una breve adición.

  • Depuración Algunos IDE luchan con el alcance dentro de una lambda, y luchan por mostrar las variables miembro dentro del contexto de una lambda. Si bien es de esperar que esta situación cambie en el futuro, puede ser molesto mantener el código de otra persona cuando está lleno de lambdas.
Jolleyboy
fuente
Definitivamente cierto de .NET. No me obliga a evitar las lambdas, necesariamente, pero ciertamente no siento ninguna culpa si hago algo más.
user1172763
3
Si ese es el caso, está utilizando el IDE incorrecto. Tanto IntelliJ como Netbeans funcionan bastante bien en esa área en particular.
David Foerster
1
@ user1172763 En Visual Studio, si desea ver las variables miembro, descubrí que puede subir la pila de llamadas hasta llegar al contexto de la lambda.
Zev Spitz
6

Acceso a variables locales del ámbito de aplicación.

La respuesta aceptada por Karl Bielefeldt es correcta. Puedo agregar una distinción más:

  • Alcance

El código lambda anidado dentro de un método dentro de una clase puede acceder a cualquier variable final efectiva encontrada dentro de ese método y clase.

Crear una clase que implemente la interfaz funcional no le brinda dicho acceso directo al estado del código de llamada.

Para citar el Tutorial de Java (énfasis mío):

Al igual que las clases locales y anónimas, las expresiones lambda pueden capturar variables; tienen el mismo acceso a las variables locales del ámbito de inclusión . Sin embargo, a diferencia de las clases locales y anónimas, las expresiones lambda no tienen ningún problema de sombreado (consulte Sombreado para obtener más información). Las expresiones lambda tienen un ámbito léxico. Esto significa que no heredan ningún nombre de un supertipo ni introducen un nuevo nivel de alcance. Las declaraciones en una expresión lambda se interpretan tal como se encuentran en el entorno circundante.

Por lo tanto, aunque hay beneficios al extraer un código largo y nombrarlo, debe sopesarlo con la simplicidad del acceso directo al estado del método y la clase adjuntos.

Ver:

Albahaca Bourque
fuente
4

Esto podría ser una trampa, pero a todos los otros puntos excelentes hechos en otras respuestas, agregaría:

Prefiera las referencias de métodos cuando sea posible. Comparar:

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

versus

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

El uso de una referencia de método le ahorra la necesidad de nombrar los argumentos de lambda, que generalmente son redundantes y / o conducen a nombres perezosos como eo x.

Matt McHenry
fuente
0

Sobre la base de la respuesta de Matt McHenry, puede haber una relación entre las referencias de método y lambda donde la lambda puede reescribirse como una serie de referencias de método. Por ejemplo:

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

vs

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