Comience con las expresiones lambda en Java

Antes de Java SE 8, las clases anónimas se usaban normalmente para pasar funcionalidad a un método. Esta práctica ofusca el código fuente, haciéndolo más difícil de entender. Java 8 eliminó este problema al introducir lambdas. Este tutorial primero presenta la característica del lenguaje lambda, luego proporciona una introducción más detallada a la programación funcional con expresiones lambda junto con los tipos de destino. También aprenderá cómo interactúan con lambdas alcances, las variables locales, thisy superlas palabras clave, y las excepciones de Java. 

Tenga en cuenta que los ejemplos de código de este tutorial son compatibles con JDK 12.

Descubriendo tipos por ti mismo

No presentaré ninguna característica de lenguaje que no sea lambda en este tutorial que no haya aprendido previamente, pero demostraré lambdas a través de tipos que no he discutido anteriormente en esta serie. Un ejemplo es la java.lang.Mathclase. Presentaré estos tipos en futuros tutoriales de Java 101. Por ahora, sugiero leer la documentación de la API JDK 12 para obtener más información sobre ellos.

descargar Obtener el código Descargar el código fuente, por ejemplo, las aplicaciones de este tutorial. Creado por Jeff Friesen para JavaWorld.

Lambdas: una cartilla

Una expresión lambda (lambda) describe un bloque de código (una función anónima) que se puede pasar a constructores o métodos para su posterior ejecución. El constructor o método recibe el lambda como argumento. Considere el siguiente ejemplo:

() -> System.out.println("Hello")

Este ejemplo identifica una lambda para enviar un mensaje al flujo de salida estándar. De izquierda a derecha, ()identifica la lista de parámetros formales de la lambda (no hay parámetros en el ejemplo), ->indica que la expresión es una lambda y System.out.println("Hello")es el código a ejecutar.

Lambdas simplifica el uso de interfaces funcionales , que son interfaces anotadas que declaran cada una exactamente un método abstracto (aunque también pueden declarar cualquier combinación de métodos predeterminados, estáticos y privados). Por ejemplo, la biblioteca de clases estándar proporciona una java.lang.Runnableinterfaz con un único void run()método abstracto . La declaración de esta interfaz funcional aparece a continuación:

@FunctionalInterface public interface Runnable { public abstract void run(); }

La biblioteca de clases anota Runnablecon @FunctionalInterface, que es una instancia del java.lang.FunctionalInterfacetipo de anotación. FunctionalInterfacese utiliza para anotar las interfaces que se utilizarán en contextos lambda.

Una lambda no tiene un tipo de interfaz explícito. En cambio, el compilador usa el contexto circundante para inferir qué interfaz funcional instanciar cuando se especifica una lambda; la lambda está vinculada a esa interfaz. Por ejemplo, supongamos que especifiqué el siguiente fragmento de código, que pasa la lambda anterior como argumento al constructor de la java.lang.Threadclase Thread(Runnable target):

new Thread(() -> System.out.println("Hello"));

El compilador determina que se está pasando el lambda Thread(Runnable r)porque este es el único constructor que satisface el lambda: Runnablees una interfaz funcional, la lista de parámetros formales vacíos del lambda ()coincide run()con la lista de parámetros vacíos y los tipos de retorno ( void) también están de acuerdo. La lambda está vinculada a Runnable.

El Listado 1 presenta el código fuente a una pequeña aplicación que le permite jugar con este ejemplo.

Listado 1. LambdaDemo.java (versión 1)

public class LambdaDemo { public static void main(String[] args) { new Thread(() -> System.out.println("Hello")).start(); } }

Compile el Listado 1 ( javac LambdaDemo.java) y ejecute la aplicación ( java LambdaDemo). Debe observar el siguiente resultado:

Hello

Lambdas puede simplificar enormemente la cantidad de código fuente que debe escribir y también puede hacer que el código fuente sea mucho más fácil de entender. Por ejemplo, sin lambdas, probablemente especificaría el código más detallado del Listado 2, que se basa en una instancia de una clase anónima que implementa Runnable.

Listado 2. LambdaDemo.java (versión 2)

public class LambdaDemo { public static void main(String[] args) { Runnable r = new Runnable() { @Override public void run() { System.out.println("Hello"); } }; new Thread(r).start(); } }

Después de compilar este código fuente, ejecute la aplicación. Descubrirá el mismo resultado que se mostró anteriormente.

Lambdas y la API de Streams

Además de simplificar el código fuente, las lambdas juegan un papel importante en la API de Streams orientada funcionalmente de Java. Describen unidades de funcionalidad que se pasan a varios métodos API.

Lambdas de Java en profundidad

Para usar lambdas de manera efectiva, debe comprender la sintaxis de las expresiones lambda junto con la noción de un tipo de destino. También es necesario entender cómo interactúan con lambdas alcances, las variables locales, thisy superlas palabras clave, y las excepciones. Cubriré todos estos temas en las secciones que siguen.

Cómo se implementan las lambdas

Las lambdas se implementan en términos de la invokedynamicinstrucción de la máquina virtual Java y la java.lang.invokeAPI. Vea el video Lambda: Un vistazo bajo el capó para aprender sobre la arquitectura lambda.

Sintaxis lambda

Cada lambda se ajusta a la siguiente sintaxis:

( formal-parameter-list ) -> { expression-or-statements }

El formal-parameter-listes una lista separada por comas de parámetros formales, que deben coincidir con los parámetros del método abstracto único de una interfaz funcional en tiempo de ejecución. Si omite sus tipos, el compilador infiere estos tipos del contexto en el que se usa la lambda. Considere los siguientes ejemplos:

(double a, double b) // types explicitly specified (a, b) // types inferred by compiler

Lambdas y var

A partir de Java SE 11, puede reemplazar el nombre de un tipo por var. Por ejemplo, puede especificar (var a, var b).

Debe especificar paréntesis para varios parámetros formales o ninguno. Sin embargo, puede omitir los paréntesis (aunque no es necesario) al especificar un único parámetro formal. (Esto se aplica solo al nombre del parámetro; se requieren paréntesis cuando también se especifica el tipo). Considere los siguientes ejemplos adicionales:

x // parentheses omitted due to single formal parameter (double x) // parentheses required because type is also present () // parentheses required when no formal parameters (x, y) // parentheses required because of multiple formal parameters

El formal-parameter-listes seguido por un ->token, que es seguido por expression-or-statementsuna expresión o un bloque de declaraciones (cualquiera de los dos se conoce como el cuerpo de lambda). A diferencia de los cuerpos basados ​​en expresiones, los cuerpos basados ​​en declaraciones deben colocarse entre llaves open ( {) y close ( }):

(double radius) -> Math.PI * radius * radius radius -> { return Math.PI * radius * radius; } radius -> { System.out.println(radius); return Math.PI * radius * radius; }

El cuerpo lambda basado en expresiones del primer ejemplo no tiene que colocarse entre llaves. El segundo ejemplo convierte el cuerpo basado en expresión en un cuerpo basado en declaración, en el que se returndebe especificar para devolver el valor de la expresión. El ejemplo final demuestra varias declaraciones y no se puede expresar sin las llaves.

Cuerpos lambda y punto y coma

Tenga en cuenta la ausencia o presencia de punto y coma ( ;) en los ejemplos anteriores. En cada caso, el cuerpo lambda no termina con un punto y coma porque lambda no es una declaración. Sin embargo, dentro de un cuerpo lambda basado en instrucciones, cada instrucción debe terminar con un punto y coma.

El Listado 3 presenta una aplicación simple que demuestra la sintaxis lambda; tenga en cuenta que esta lista se basa en los dos ejemplos de código anteriores.

Listado 3. LambdaDemo.java (versión 3)

@FunctionalInterface interface BinaryCalculator { double calculate(double value1, double value2); } @FunctionalInterface interface UnaryCalculator { double calculate(double value); } public class LambdaDemo { public static void main(String[] args) { System.out.printf("18 + 36.5 = %f%n", calculate((double v1, double v2) -> v1 + v2, 18, 36.5)); System.out.printf("89 / 2.9 = %f%n", calculate((v1, v2) -> v1 / v2, 89, 2.9)); System.out.printf("-89 = %f%n", calculate(v -> -v, 89)); System.out.printf("18 * 18 = %f%n", calculate((double v) -> v * v, 18)); } static double calculate(BinaryCalculator calc, double v1, double v2) { return calc.calculate(v1, v2); } static double calculate(UnaryCalculator calc, double v) { return calc.calculate(v); } }

El Listado 3 presenta primero las interfaces funcionales BinaryCalculatory UnaryCalculatorcuyos calculate()métodos realizan cálculos en dos argumentos de entrada o en un solo argumento de entrada, respectivamente. Este listado también presenta una LambdaDemoclase cuyo main()método demuestra estas interfaces funcionales.

The functional interfaces are demonstrated in the static double calculate(BinaryCalculator calc, double v1, double v2) and static double calculate(UnaryCalculator calc, double v) methods. The lambdas pass code as data to these methods, which are received as BinaryCalculator or UnaryCalculator instances.

Compile Listing 3 and run the application. You should observe the following output:

18 + 36.5 = 54.500000 89 / 2.9 = 30.689655 -89 = -89.000000 18 * 18 = 324.000000

Target types

A lambda is associated with an implicit target type, which identifies the type of object to which a lambda is bound. The target type must be a functional interface that's inferred from the context, which limits lambdas to appearing in the following contexts:

  • Variable declaration
  • Assignment
  • Return statement
  • Array initializer
  • Method or constructor arguments
  • Lambda body
  • Ternary conditional expression
  • Cast expression

Listing 4 presents an application that demonstrates these target type contexts.

Listado 4. LambdaDemo.java (versión 4)

import java.io.File; import java.io.FileFilter; import java.nio.file.Files; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.FileVisitor; import java.nio.file.FileVisitResult; import java.nio.file.Path; import java.nio.file.PathMatcher; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.concurrent.Callable; public class LambdaDemo { public static void main(String[] args) throws Exception { // Target type #1: variable declaration Runnable r = () -> { System.out.println("running"); }; r.run(); // Target type #2: assignment r = () -> System.out.println("running"); r.run(); // Target type #3: return statement (in getFilter()) File[] files = new File(".").listFiles(getFilter("txt")); for (int i = 0; i  path.toString().endsWith("txt"), (path) -> path.toString().endsWith("java") }; FileVisitor visitor; visitor = new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attribs) { Path name = file.getFileName(); for (int i = 0; i  System.out.println("running")).start(); // Target type #6: lambda body (a nested lambda) Callable callable = () -> () -> System.out.println("called"); callable.call().run(); // Target type #7: ternary conditional expression boolean ascendingSort = false; Comparator cmp; cmp = (ascendingSort) ? (s1, s2) -> s1.compareTo(s2) : (s1, s2) -> s2.compareTo(s1); List cities = Arrays.asList("Washington", "London", "Rome", "Berlin", "Jerusalem", "Ottawa", "Sydney", "Moscow"); Collections.sort(cities, cmp); for (int i = 0; i < cities.size(); i++) System.out.println(cities.get(i)); // Target type #8: cast expression String user = AccessController.doPrivileged((PrivilegedAction) () -> System.getProperty("user.name")); System.out.println(user); } static FileFilter getFilter(String ext) { return (pathname) -> pathname.toString().endsWith(ext); } }