Expresiones Java Lambda (con ejemplos)

En este artículo, aprenderemos sobre la expresión lambda de Java y el uso de la expresión lambda con interfaces funcionales, interfaz funcional genérica y API de flujo con la ayuda de ejemplos.

La expresión lambda se introdujo por primera vez en Java 8. Su principal objetivo es aumentar el poder expresivo del lenguaje.

Pero, antes de entrar en lambdas, primero debemos comprender las interfaces funcionales.

¿Qué es la interfaz funcional?

Si una interfaz Java contiene uno y solo un método abstracto, se denomina interfaz funcional. Este único método especifica el propósito previsto de la interfaz.

Por ejemplo, la Runnableinterfaz del paquete java.lang; es una interfaz funcional porque constituye solo un método, es decir run().

Ejemplo 1: definir una interfaz funcional en java

 import java.lang.FunctionalInterface; @FunctionalInterface public interface MyInterface( // the single abstract method double getValue(); )

En el ejemplo anterior, la interfaz MyInterface solo tiene un método abstracto getValue (). Por tanto, es una interfaz funcional.

Aquí, hemos utilizado la anotación @FunctionalInterface. La anotación obliga al compilador de Java a indicar que la interfaz es una interfaz funcional. Por tanto, no permite tener más de un método abstracto. Sin embargo, no es obligatorio.

En Java 7, las interfaces funcionales se consideraron como métodos abstractos únicos o tipo SAM . Los SAM se implementaron comúnmente con clases anónimas en Java 7.

Ejemplo 2: implementar SAM con clases anónimas en java

 public class FunctionInterfaceTest ( public static void main(String() args) ( // anonymous class new Thread(new Runnable() ( @Override public void run() ( System.out.println("I just implemented the Runnable Functional Interface."); ) )).start(); ) )

Salida :

 Acabo de implementar la interfaz funcional ejecutable.

Aquí, podemos pasar una clase anónima a un método. Esto ayuda a escribir programas con menos códigos en Java 7. Sin embargo, la sintaxis seguía siendo difícil y se necesitaban muchas líneas de código adicionales.

Java 8 amplió el poder de los SAM dando un paso más. Dado que sabemos que una interfaz funcional tiene solo un método, no debería ser necesario definir el nombre de ese método al pasarlo como argumento. La expresión lambda nos permite hacer exactamente eso.

Introducción a las expresiones lambda

La expresión lambda es, esencialmente, un método anónimo o sin nombre. La expresión lambda no se ejecuta por sí sola. En cambio, se utiliza para implementar un método definido por una interfaz funcional.

¿Cómo definir la expresión lambda en Java?

Así es como podemos definir la expresión lambda en Java.

 (parameter list) -> lambda body

El nuevo operador ( ->) utilizado se conoce como operador de flecha o operador lambda. Es posible que la sintaxis no sea clara en este momento. Exploremos algunos ejemplos,

Supongamos que tenemos un método como este:

 double getPiValue() ( return 3.1415; )

Podemos escribir este método usando expresión lambda como:

 () -> 3.1415

Aquí, el método no tiene ningún parámetro. Por tanto, el lado izquierdo del operador incluye un parámetro vacío. El lado derecho es el cuerpo lambda que especifica la acción de la expresión lambda. En este caso, devuelve el valor 3,1415.

Tipos de cuerpo Lambda

En Java, el cuerpo lambda es de dos tipos.

1. Un cuerpo con una sola expresión

 () -> System.out.println("Lambdas are great");

Este tipo de cuerpo lambda se conoce como cuerpo de expresión.

2. Un cuerpo que consta de un bloque de código.

 () -> ( double pi = 3.1415; return pi; );

Este tipo de cuerpo lambda se conoce como cuerpo de bloque. El cuerpo del bloque permite que el cuerpo lambda incluya varias declaraciones. Estas declaraciones se incluyen dentro de las llaves y debe agregar un punto y coma después de las llaves.

Nota : Para el cuerpo del bloque, puede tener una declaración de retorno si el cuerpo devuelve un valor. Sin embargo, el cuerpo de la expresión no requiere una declaración de devolución.

Ejemplo 3: Expresión Lambda

Escribamos un programa Java que devuelva el valor de Pi usando la expresión lambda.

Como se mencionó anteriormente, una expresión lambda no se ejecuta por sí sola. Más bien, forma la implementación del método abstracto definido por la interfaz funcional.

So, we need to define a functional interface first.

 import java.lang.FunctionalInterface; // this is functional interface @FunctionalInterface interface MyInterface( // abstract method double getPiValue(); ) public class Main ( public static void main( String() args ) ( // declare a reference to MyInterface MyInterface ref; // lambda expression ref = () -> 3.1415; System.out.println("Value of Pi = " + ref.getPiValue()); ) )

Output:

 Value of Pi = 3.1415

In the above example,

  • We have created a functional interface named MyInterface. It contains a single abstract method named getPiValue()
  • Inside the Main class, we have declared a reference to MyInterface. Note that we can declare a reference of an interface but we cannot instantiate an interface. That is,
     // it will throw an error MyInterface ref = new myInterface(); // it is valid MyInterface ref;
  • We then assigned a lambda expression to the reference.
     ref = () -> 3.1415;
  • Finally, we call the method getPiValue() using the reference interface. When
     System.out.println("Value of Pi = " + ref.getPiValue());

Lambda Expressions with parameters

Till now we have created lambda expressions without any parameters. However, similar to methods, lambda expressions can also have parameters. For example,

 (n) -> (n%2)==0

Here, the variable n inside the parenthesis is a parameter passed to the lambda expression. The lambda body takes the parameter and checks if it is even or odd.

Example 4: Using lambda expression with parameters

 @FunctionalInterface interface MyInterface ( // abstract method String reverse(String n); ) public class Main ( public static void main( String() args ) ( // declare a reference to MyInterface // assign a lambda expression to the reference MyInterface ref = (str) -> ( String result = ""; for (int i = str.length()-1; i>= 0 ; i--) result += str.charAt(i); return result; ); // call the method of the interface System.out.println("Lambda reversed = " + ref.reverse("Lambda")); ) )

Output:

 Lambda reversed = adbmaL

Generic Functional Interface

Till now we have used the functional interface that accepts only one type of value. For example,

 @FunctionalInterface interface MyInterface ( String reverseString(String n); )

The above functional interface only accepts String and returns String. However, we can make the functional interface generic, so that any data type is accepted. If you are not sure about generics, visit Java Generics.

Example 5: Generic Functional Interface and Lambda Expressions

 // GenericInterface.java @FunctionalInterface interface GenericInterface ( // generic method T func(T t); ) // GenericLambda.java public class Main ( public static void main( String() args ) ( // declare a reference to GenericInterface // the GenericInterface operates on String data // assign a lambda expression to it GenericInterface reverse = (str) -> ( String result = ""; for (int i = str.length()-1; i>= 0 ; i--) result += str.charAt(i); return result; ); System.out.println("Lambda reversed = " + reverse.func("Lambda")); // declare another reference to GenericInterface // the GenericInterface operates on Integer data // assign a lambda expression to it GenericInterface factorial = (n) -> ( int result = 1; for (int i = 1; i <= n; i++) result = i * result; return result; ); System.out.println("factorial of 5 = " + factorial.func(5)); ) )

Output:

 Lambda reversed = adbmaL factorial of 5 = 120

In the above example, we have created a generic functional interface named GenericInterface. It contains a generic method named func().

Here, inside the Main class,

  • GenericInterface reverse - creates a reference to the interface. The interface now operates on String type of data.
  • GenericInterface factorial - creates a reference to the interface. The interface, in this case, operates on the Integer type of data.

Lambda Expression and Stream API

The new java.util.stream package has been added to JDK8 which allows java developers to perform operations like search, filter, map, reduce, or manipulate collections like Lists.

For example, we have a stream of data (in our case a List of String) where each string is a combination of country name and place of the country. Now, we can process this stream of data and retrieve only the places from Nepal.

For this, we can perform bulk operations in the stream by the combination of Stream API and Lambda expression.

Example 6: Demonstration of using lambdas with the Stream API

 import java.util.ArrayList; import java.util.List; public class StreamMain ( // create an object of list using ArrayList static List places = new ArrayList(); // preparing our data public static List getPlaces()( // add places and country to the list places.add("Nepal, Kathmandu"); places.add("Nepal, Pokhara"); places.add("India, Delhi"); places.add("USA, New York"); places.add("Africa, Nigeria"); return places; ) public static void main( String() args ) ( List myPlaces = getPlaces(); System.out.println("Places from Nepal:"); // Filter places from Nepal myPlaces.stream() .filter((p) -> p.startsWith("Nepal")) .map((p) -> p.toUpperCase()) .sorted() .forEach((p) -> System.out.println(p)); ) )

Output:

 Places from Nepal: NEPAL, KATHMANDU NEPAL, POKHARA

In the above example, notice the statement,

 myPlaces.stream() .filter((p) -> p.startsWith("Nepal")) .map((p) -> p.toUpperCase()) .sorted() .forEach((p) -> System.out.println(p));

Here, we are using the methods like filter(), map() and forEach() of the Stream API. These methods can take a lambda expression as input.

También podemos definir nuestras propias expresiones basándonos en la sintaxis que aprendimos anteriormente. Esto nos permite reducir las líneas de código drásticamente como vimos en el ejemplo anterior.

Articulos interesantes...