Saltar a contenido

Expresiones lambda

Las funciones lambdas es un término adoptado de la programación funcional y corresponden con funciones de Java que normalmente son anónimas y se escriben en línea allí donde se usan.

Una expresión lambda como cualquier función recibe cero o más argumentos y devuelven uno o ningún valor de retorno. Como cualquier función, puede consumir métodos de otras clases y objetos. Al declararse al mismo tiempo en donde se usa, puede acceder a las variables locales del ámbito al que pertenece, pero sólo podrá usar estos como valores de sólo lectura, impidiendo realizar alguna modificación

Interfaces funcionales

Una interfaz funcional es aquella que contiene un único método abstracto. Esto NO quiere decir que no pueda contener otros métodos static, default, etc. Java proporciona la notación @FunctionalInterface que alertará al programador si por error ha incluido más de un método abstracto en una interfaz considerada funcional.

La API Java dispone de multitud de interfaces funcionales. Un ejemplo de ello es la interfaz Comparator, la cual contiene un único método abstracto (compare()).

@FunctionalInterface
public interface Comparator<T>
Ya hemos visto como utilizar esta Interfaz, y como podemos crear clases anónimas para manejar Comparator e implementar su método compare. Veremos como las interfaces funcionales están relacionadas con las expresiones lambdas.

Sintaxis de lambda

Dado que las expresiones lambda son efectivamente solo métodos, las expresiones lambda pueden tomar parámetros como los métodos.

La expresión lambda de Java consta de tres componentes.

  1. Parámetros - lista de argumentos: una expresión lambda puede tener cero o cualquier número de argumentos.

  2. Token de flecha(->): se utiliza para vincular la lista de argumentos y el cuerpo de la expresión.

  3. Cuerpo: Contiene expresiones y declaraciones para la expresión lambda.

lambda

Las expresiones lambda se pueden almacenar en variables si el tipo de variable es una interface que tiene un solo método. La expresión lambda debe tener la misma cantidad de parámetros y el mismo tipo de retorno que ese método.

Ejemplos de lambdas

Cero parámetros

Los paréntesis no tienen contenido en el medio. Eso es para indicar que la expresión lambda no recibe parámetros.

() -> {body}

//Si tenemos la siguiente interface
interface Saludo{  
    public String say();  
}  

public class Main {
    //mediante clases anónimas
    Saludo holaSpain =new Saludo() {
        @Override
        public String say() {
            return "Hola...";
        }
    };
    //mediante lambdas
    Saludo holaPortugues=()->{
        return "Olá...";
    };
    Saludo holaIngles=()-> {
        return "Hello...";
    };
    //llamamos al método say
    System.out.println(holaSpain.say());
    System.out.println(holaPortugues.say());
    System.out.println(holaIngles.say());
}
Cuando el cuerpo de la lambda solo tiene una línea podemos abreviar

Saludo holaSpain = () -> "Hola...";       
Saludo holaPortugues=()->"Olá...";
Saludo holaIngles=()-> "Hello...";

Un parámetro

Cuando una expresión lambda recibe un solo parámetro, también se puede omitir los paréntesis, de forma que quedaría así:

(p1) -> {body}

p1 -> {body}

interface Saludo{  
    public String say(String nombre);  
}

public class Main {

  public static void main(String[] args) { 
    //con parentesis
    Saludo holaSpain = (nombre) -> "Hola..."+nombre;
    Saludo holaPortugues=(nombre)->"Olá..."+nombre;
    //sin parentesis
    Saludo holaIngles=nombre-> "Hello..."+nombre;

    //llamamos al método say
    System.out.println(holaSpain.say("Pepe"));
    System.out.println(holaPortugues.say("Maria"));
    System.out.println(holaIngles.say("Pablo"));
    }  
}

Múltiples parámetros

Si el método con el que coincide su expresión lambda de Java recibe varios parámetros, los parámetros deben enumerarse entre paréntesis. Así es como se ve en código Java:

(p1, p2) -> {body}

interface Operar{  
    int opera(int a,int b);  
}  

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

        Operar suma=(a,b)->(a+b);  
        System.out.println(suma.opera(10,20));  

        // con tipo de datos  
        Operar mult=(int a,int b)->(a*b);  
        System.out.println(mult.opera(100,200));  
    }  
} 

Tipo de parámetros

En ocasiones, puede ser necesario especificar tipos de parámetros para una expresión lambda si el compilador no puede inferir los tipos de parámetros del método de interfaz funcional con el que coincide la lambda.

(Coche coche) -> System.out.println("El coche es: " + coche.getName());

Comparable y Comparator mediante lambdas

Las lambdas nos van a permitir manejar las interfaces de la API de Java de una forma mucho más reducida. Si recordamos los ejemplos que vimos en el capítulo de Interface. Teniendo en cuenta que el método sort espera una implementación de Comparator, podemos reducir el código mediante lambdas.

class Persona {
     String nombre;
     int edad;

    public Persona(String nombre, int edad) {
        this.nombre = nombre;
        this.edad = edad;
    }

    public static void main(String[] args) {
        // Crear una ArrayList de personas
        ArrayList<Persona> listaPersonas = new ArrayList<>();

        // Agregar algunas personas a la lista
        listaPersonas.add(new Persona("Juan", 25));
        listaPersonas.add(new Persona("Maria", 30));
        listaPersonas.add(new Persona("Carlos", 42));
        //Ordenar por nombre
        listaPersonas.sort((p1,p2)->p1.nombre.compareTo(p2.nombre));
        //Ordenar por edad
        listaPersonas.sort((p1,p2)->p1.edad-p2.edad);


    }
}

Bucle for-each en Collections

Podemos utilizar el método foreach de las listas que permite pasarle una lambda que se ejecuta para cada elemento de la lista

public static void main(String[] args) {  

    List<String> list = new ArrayList<String>();  
    list.add("java");
    list.add("lambda");
    list.add("test");  

    list.forEach(  
        //n es el elemento actual de la lista
        (n)->System.out.println(n)  
    );  

    List<Integer> list2=new ArrayList<Integer>();
    list2.add(1);
    list2.add((2));
    list2.add(3);    
    list2.forEach(n->System.out.println(n%2==0?"Es par":"Es impar"));
}

Escenario sin expresiones lambda

interface Saludo { 
    public void saludar();  
}  
public class LambdaExpressionExample {  
    public static void main(String[] args) {  
        String nombre = "Patri";

        //sin expresiones lambda, Saludo se implementa usando clases anónimas
        Saludo s = new Saludo(){  
            public void saludar(){System.out.println("Hola " + nombre);}  
        };  
        s.saludar();  
    }  
}  

Escenario con expresiones lambda

@FunctionalInterface  //Esto es opcional
interface Saludo {  
    public void saludar();  
}  

public class LambdaExpressionExample2 {  
    public static void main(String[] args) {  
        String nombre = "Patri";

        //con expresiones lambda
        Saludo s2 = ()-> {
            System.out.println("Hola " + nombre);  
        };
        s2.saludar();  
    }  
}  

Function Crear una expresión lambda sin interface

Para definir una expresión lambda de un método que no está definido en ninguna interfaz funcional, tenemos que utilizar la clase Function. A la clase Function se le especifican dos tipos de parámetros entre <>, el primero es el parámetro de entrada de la función, y el segundo es el parámetro de salida que devuelve la función o método.

    Function<String, Integer> funcionLambda = (s) -> {
            int total = 0;
            for (int i = 0; i < s.length(); i++) {
                //suma el valor del código Unicode del carcter
                total+=s.charAt(i);
            }
            return total;
        };

Para ejecutar el código en el interior de la expresión lambda, utilizaremos el método apply:

System.out.println(funcionLambda.apply("Programación"));