Expresiones lambda en Java

¡Hola, Habr! Les presento a su atención la traducción del artículo "Java Lambda Expressions" del autor de www.programiz.com .



Introducción



En este artículo, utilizando ejemplos, exploraremos las expresiones lambda en Java, su uso con interfaces funcionales, interfaces funcionales parametrizadas y API de transmisión.



Las expresiones Lambda se agregaron en Java 8. Su propósito principal es mejorar la legibilidad y reducir la cantidad de código.



Pero antes de pasar a lambdas, necesitamos comprender las interfaces funcionales.



¿Qué es una interfaz funcional?



Si una interfaz en Java contiene uno y solo un método abstracto, entonces se llama funcional. Este único método determina el propósito de la interfaz.



Por ejemplo, la interfaz Runnable del paquete java.lang es funcional porque contiene solo un método run ().



Ejemplo 1: declarar una interfaz funcional en java



import java.lang.FunctionalInterface;
@FunctionalInterface
public interface MyInterface{
    //   
    double getValue();
}


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



Aquí hemos utilizado la anotaciónInterfaz funcionallo que ayuda al compilador a comprender que la interfaz es funcional. Por lo tanto, no le permite tener más de un método abstracto. Sin embargo, podemos omitirlo.



En Java 7, las interfaces funcionales se consideraban métodos abstractos únicos (SAM). Los SAM se implementaron generalmente mediante clases anónimas.



Ejemplo 2: implementación de SAM con clase anónima en java



public class FunctionInterfaceTest {
    public static void main(String[] args) {

        //  
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("      Runnable.")
            }
        }).start();
    }
}


Resultado de ejecución:



      Runnable.


En este ejemplo, aceptamos una clase anónima para llamar al método. Esto ayudó a escribir programas con menos líneas de código en Java 7. Sin embargo, la sintaxis siguió siendo bastante compleja y engorrosa.



Java 8 ha ampliado las capacidades de SAM, dando un paso más. Como sabemos, la interfaz funcional contiene solo un método, por lo tanto, no necesitamos especificar el nombre del método al pasarlo como argumento. Esto es exactamente lo que nos permiten hacer las expresiones lambda.



Introducción a las expresiones lambda



Las expresiones lambda son esencialmente una clase o método anónimo. La expresión lambda no se ejecuta por sí sola. En cambio, se utiliza para implementar el método definido en la interfaz funcional.



¿Cómo escribo una expresión lambda en Java?



En Java, las expresiones lambda tienen la siguiente sintaxis:



(parameter list) -> lambda body


Aquí hemos utilizado un nuevo operador (->): el operador lambda. Quizás la sintaxis parezca un poco complicada. Tomemos un par de ejemplos.



Digamos que tenemos un método como este:



double getPiValue() {
    return 3.1415;
}


Podemos escribirlo usando una lambda como:



() -> 3.1415


Este método no tiene parámetros. Por lo tanto, el lado izquierdo de la expresión contiene paréntesis vacíos. El lado derecho es el cuerpo de la expresión lambda, que define su acción. En nuestro caso, el valor de retorno es 3,1415.



Tipos de expresiones lambda



En Java, el cuerpo de una lambda puede ser de dos tipos.



1. Una línea



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


2. Bloque (varias líneas)



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


Este tipo permite que una expresión lambda tenga múltiples operaciones internamente. Estas operaciones deben ir entre llaves, seguidas de un punto y coma.



Nota: Las expresiones lambda de varias líneas siempre deben tener una declaración de retorno, a diferencia de las de una sola línea.



Ejemplo 3: Expresión Lambda



Escribamos un programa Java que devuelva Pi utilizando una expresión lambda.



Como se indicó anteriormente, una expresión lambda no se ejecuta automáticamente. Más bien, forma una implementación de un método abstracto declarado en una interfaz funcional.



Entonces, primero, necesitamos describir la interfaz funcional.



import java.lang.FunctionalInterface;

//  
@FunctionalInterface
interface MyInterface{

    //  
    double getPiValue();
}
public class Main {

    public static void main( String[] args ) {

    //    MyInterface
    MyInterface ref;
    
    // -
    ref = () -> 3.1415;
    
    System.out.println("Value of Pi = " + ref.getPiValue());
    } 
}


Resultado de ejecución:



Value of Pi = 3.1415


En el ejemplo anterior:



  • Hemos creado una interfaz funcional, MyInterface, que contiene un método abstracto, getPiValue ().
  • Dentro de la clase Main, hemos declarado una referencia a MyInterface. Tenga en cuenta que podemos declarar una referencia de interfaz, pero no podemos crear un objeto de ella.



    //   
    	MyInterface ref = new myInterface();
    	//  
    	MyInterface ref;
    


  • Luego asignamos la expresión lambda al enlace



    ref = () -> 3.1415;
  • Finalmente, llamamos al método getPiValue () usando la referencia de interfaz.



    System.out.println("Value of Pi = " + ref.getPiValue());


Expresiones lambda con parámetros



Hasta este punto, hemos creado expresiones lambda sin ningún parámetro. Sin embargo, al igual que los métodos, las lambdas pueden tener parámetros.



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


En este ejemplo, la variable n entre paréntesis es el parámetro pasado a la expresión lambda. El cuerpo lambda toma un parámetro y comprueba la paridad.



Ejemplo 4: uso de una expresión lambda con parámetros



@FunctionalInterface
interface MyInterface {

    //  
    String reverse(String n);
}

public class Main {

    public static void main( String[] args ) {

        //    MyInterface
        //  - 
        MyInterface ref = (str) -> {

            String result = "";
            for (int i = str.length()-1; i >= 0 ; i--)
            result += str.charAt(i);
            return result;
        };
        //    
        System.out.println("Lambda reversed = " + ref.reverse("Lambda"));
    }

}


Resultado de ejecución:



Lambda reversed = adbmaL


Interfaz funcional parametrizada



Hasta este punto, hemos utilizado interfaces funcionales que solo aceptan un tipo de valor. Por ejemplo:



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


La interfaz funcional anterior solo acepta String y devuelve String. Sin embargo, podemos hacer que nuestra interfaz sea genérica para usar con cualquier tipo de datos.



Ejemplo 5: Interfaz parametrizada y expresiones Lambda



//  
@FunctionalInterface
interface GenericInterface<T> {

    //  
    T func(T t);
}

public class Main {

    public static void main( String[] args ) {

        //     
        //   String
        //    
        GenericInterface<String> 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"));

        //     
        //   Integer
        //    
        GenericInterface<Integer> 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));
    }
}


Resultado de ejecución:



Lambda reversed = adbmaL
factorial of 5 = 120


En este ejemplo, hemos creado una interfaz funcional parametrizada GenericInterface que contiene un método func () parametrizado.



Luego, dentro de la clase Main:



  • GenericInterface <String> reverse: crea un enlace a una interfaz que funciona con String.
  • GenericInterface <Integer> factorial: crea un enlace a una interfaz que funciona con Integer.


Expresiones Lambda y API Stream



El JDK8 agrega un nuevo paquete, java.util.stream, que permite a los desarrolladores de Java realizar operaciones como buscar, filtrar, emparejar, fusionar o manipular colecciones como Listas.



Por ejemplo, tenemos un flujo de datos (en nuestro caso, una lista de cadenas), donde cada cadena contiene el nombre de un país y su ciudad. Ahora podemos procesar este flujo de datos y seleccionar solo las ciudades de Nepal.



Para hacer esto, podemos usar una combinación de Stream API y expresiones lambda.



Ejemplo 6: uso de Lambdas en la API de transmisión



import java.util.ArrayList;
import java.util.List;

public class StreamMain {

    //  
    static List<String> places = new ArrayList<>();

    //  
    public static List getPlaces(){

        //    
        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<String> myPlaces = getPlaces();
        System.out.println("Places from Nepal:");
        
        //  
        myPlaces.stream()
                .filter((p) -> p.startsWith("Nepal"))
                .map((p) -> p.toUpperCase())
                .sorted()
                .forEach((p) -> System.out.println(p));
    }

}


Resultado de ejecución:



Places from Nepal:
NEPAL, KATHMANDU
NEPAL, POKHARA


En el ejemplo anterior, observe esta expresión:



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


Aquí usamos métodos como filter (), map (), forEach () de la API Stream, que puede tomar lambdas como parámetro.



Además, podemos describir nuestras propias expresiones basándonos en la sintaxis descrita anteriormente. Esto nos permitirá reducir la cantidad de líneas de código.



All Articles