Saltar al contenido

Entendiendo paso a paso las expresiones Lambda en Java 8

expresiones lambda en java

Tutorial de expresiones Lambda en Java 8 paso a paso

Hola que tal, esta vez vamos a ver las expresiones lambda una de las novedades más importantes que se ha tenido para la versión Java 8, la idea de las expresiones lambda es tener un código más limpio y legible, y aunque para programadores de otros lenguajes como C#, JavaScript, Python ya son utilizadas, para nosotros los programadores Java esto es nuevo.

En esta entrada trato de explicar cuales son los fundamentos de las Expresiones Lambda en Java. Si deseas ver ejemplos más prácticos, por ejemplo como hacer consultas sobre una lista, obtener el máximo, mínimo, o hacer algún filtro utilizando alguna condición puedes ir directamente a esta entrada Ejemplos prácticos de Expresiones Lambda.

Bien, antes de empezar a ver todo el trasfondo y cambios que tuvo la implementación  de las expresiones lambda en Java veamos un ejemplo muy sencillo recorriendo una lista de números.

Recorrer una lista de números en versiones anteriores  de Java

        for (Integer numero : Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) {
			System.out.print(numero + " ");
		}

Recorrer una lista de números utilizando expresiones Lambda en Java

       // imprimir una lista utilizando expresiones lambda en Java 8
		Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).forEach(
				n -> System.out.print(n + " "));

		// otra forma utilizando expresiones Lambdas
		Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).forEach(
				System.out::println);

Veamos otro ejemplo tenemos almacenados números del 1 al 10 en una lista y queremos obtener los números mayores a 5 y almacenarlos en una nueva lista.

En versiones anteriores de Java

          ArrayList<Integer> menor= new ArrayList<>();
		//filtrar los número mayores que 5 y añadirlos en la lista menor
		for (Integer numero : Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) {
			if (numero>5) {
				menor.add(numero);
			}
		}
		
		// imprimir la lista con los números mayores que 5
		for (Integer integer : menor) {
			System.out.println(integer);
		}

Y utilizando expresiones Lambda.

Utilizando expresiones Lambda en Java 8

		ArrayList<Integer> mayores = (ArrayList<Integer>) Arrays
				.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
				// se crea el stream
				.stream()
				//filtro para obtener los números mayores a 5 y dentro se utiliza la expresión lambda (x -> x > 5)
				.filter(x -> x > 5)
				//pone los elementos que se filtró dentro de una nueva lista, dentro se utiliza una expresión lambda  
				.collect(
						Collectors.toCollection(() -> new ArrayList<Integer>()));

		mayores.forEach(e -> System.out.println(e));

Como se puede ver el cambio entre versiones anteriores y Java 8 es bastante bueno de hecho se puede hacer en 2 líneas de código  todo lo que en versiones anteriores se lo hace entre 9 y 12 líneas.

Bueno esto es muy bueno, pero no todo es así de sencillo tras de esto hay algunas cosas que se debe tener claro para comprender las expresiones Lambda.

Qué es una expresión Lambda?

Una expresión Lambda es una función anónima, básicamente es un método abstracto es decir un método que sólo está definido en una interfaz pero no implementado, y esa es la clave de la funciones lambda, al no estar implementado, el programador lo puede implementar dónde el crea conveniente sin haber heredado de la interfaz.

Si te diste cuenta en el primer ejemplo se puede imprimir de dos formas una lista, la primera es forEach(n -> System.out.print(n + » «), mientras que la segunda es forEach(System.out::println), en los dos casos este método acepta expresiones Lambda, yendo más a fondo lo que acepta como parámetro es un Consumer<Tipo>, un consumidor que lo veremos a fondo en lo que viene del tutorial .

Bien tal vez te suena un poco confuso y más aún si no has leído o nos tienes muchos conocimientos de la POO en Java, por eso te pido leas el tema de interfaces, clases abstractas, herencia de manera que te enganches de mejor manera al tema de Lambdas

La sintaxis de un expresión suele darse de la siguiente forma:

(argumentos)->{cuerpo}

Por ejemplo:

(arg1, arg2…) -> { cuerpo}

Esta sintaxis puede cambiar tanto como para los argumentos o el cuerpo de la expresión Lambda  de acuerdo a lo siguiente.

Para los argumentos:

Los argumentos de una función Lambda pueden ser declarados explícitamente o a su vez pueden ser inferidos por el compilador de acuerdo al contexto, cuando digo de acuerdo al contexto, me refiero a que si el método que estamos implementado recibe una cadena el compilador asumirá que el argumento es una cadena y así con el tipo de dato que estuviéramos recibiendo en el método.

Entonces un argumento se puede declarar explícitamente, esto se refiere al tipo de dato, por ejemplo: (int x)-> {cuerpo} y si tiene más parámetros sería (int x, int y….)-> {cuerpo}.

También se lo puede hacer de forma implícita por ejemplo: (x)-> {cuerpo}, de echo si existe un sólo argumento y se lo declara de forma implícita puede ir incluso sin paréntesis por ejemplo x -> {cuerpo}, si se declara más de un parámetro obligatoriamente deben ir seguido de comas y entre paréntesis.

Por último puede haber expresiones Lambda en las que no hayan argumentos y se expresan de la siguiente forma: ()-> {cuerpo}.

Para el cuerpo de la expresión:

El cuerpo de la expresión puede ir o no dentro de llaves esto puede variar como se ve en los siguientes ejemplos.

Es obligatorio que el cuerpo de una expresión vaya entre llaves en los siguientes casos:

Cuando devuelve más de un valor o la sentencia tiene más de una instrucción por ejemplo:

(int a, int b) -> {  return a + b; }.

() -> { return 3.1415 }.

Mientras que cuando devuelve un sólo valor no es obligatorio, de todas maneras el compilador no muestra error si se pone entre llaves por ejemplo, (aunque hay algunas excepciones):

() -> 10

n -> System.out.print(n + » «)

Pero se la puede encerrar dentro de llaves, para esto es necesario añadir al final de la sentencia un punto y coma:

n -> {System.out.print(n + » «);}

() -> new ArrayList<Integer>().

Que es una interfaz funcional?

Bien, las expresiones Lambda van de la mano con las interfaces funcionales, el concepto de interfaz funcional es añadido con la versión de Java 8 dada la necesidad de las expresiones Lambda.

Una interfaz funcional guarda el mismo concepto que una interfaz en las anteriores versiones de Java, salvo que se añade 2 reglas y es que para que una interfaz sea funcional debe:

  1. Tener un sólo método abstracto.
  2. Una interfaz funcional debe implementar los métodos dentro la misma interfaz (esto no se podía hacer en versiones anteriores), para esto se debe anteponer la palabra reservada default al inicio de la declaración del método.

Nota: Claro está que sólo un método debe ser abstracto, si la interfaz tuviera más métodos estos deben implementarse dentro de la misma.

Así mismo aunque es opcional se puede declarar la anotación @FunctionalInterface, esta anotación le indica al compilador que esta es una interfaz funcional.

Veamos un ejemplo sencillo, una interfaz funcional que declara un método para sumar dos números enteros y que retorna su valor, si te das cuenta el método sólo se declara.

@FunctionalInterface
public interface IFuncionLambda {
	//método abstracto para sumar 2 números, que lo implementará el programador a partir de una expresión Lambda
	public void suma(int a, int b);	
}

Ahora viene la implementación del método sumar, en este caso los parámetros está declarados de forma implícita (a, b) de manera que el compilador empareje el tipo de dato con el que se encuentra en el método sumar.

Luego tenemos el cuerpo que es la implementación del método y que viene a ser { System.out.println(a + b); }.  

package com.ecodeup.com;

public class TestLambdas {

	public static void main(String[] args) {

		int x = 10;
		int y = 5;
		
		//se implementa el método de la interfaz con una expresión lambda
		IFuncionLambda iflambda = (a, b) -> {
			System.out.println(a + b);
		};
		//se utiliza el método con la implementación y se le envía los valores de x e y
		iflambda.suma(x, y);
	}
}

En conclusión si te das cuenta una expresión Lambda puede utilizarse donde haya una interfaz funcional que tenga la declaración del método que utiliza la expresión.

Las expresiones Lambda se pueden dividir de la siguiente manera:

Predicados

Los predicados son expresiones que reciben un argumento y devuelven un valor lógico por ejemplo, se usa la interface Predicate<T>:

            Predicate<String> predicate = (s) -> s.length() > 0;
		//evalua si la cadena "predicado" es mayor a 0
		System.out.println(predicate.test("predicado")); // true
		//niega la valor de la evaulación
		System.out.println(predicate.negate().test("predicado")); // false

En el siguiente ejemplo a partir de una lista de números enteros se imprime: los números pares, los números mayores a 5 y los impares.

package com.ecodeup.lambda;

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

public class Predicados {

	public static void main(String[] args) {
		List<Integer> listaNumeros = Arrays.asList(1, 2, 3, 4, 5, 6, 7,8,9,10);

		System.out.println("Números pares:");
		evaluar(listaNumeros, (n)-> n%2 == 0 );

		System.out.println("Números impares:");
		evaluar(listaNumeros, (n)-> n%2 == 1 );

		System.out.println("Números mayores a 5:");
		evaluar(listaNumeros, (n)-> n > 5 );

	}
	public static void evaluar(List<Integer> listaNumeros, Predicate<Integer> predicado) {
		for(Integer n: listaNumeros)  {
			if(predicado.test(n)) {
				System.out.print(n + " ");
			}
		}
		System.out.println();
	}
}

Funciones

Las funciones reciben un argumento y devuelven un resultado, usan la interface Function<T,R>, revisemos un ejemplo sencillo.

Function<Integer, Integer> suma = x -> x + 8;
System.out.println("La suma de 5 + 8: " + suma.apply(5));

Podemos también encontrar el tamaño de una cadena por ejemplo:

Function<String, Integer> tamanioCadena = str -> str.length();
String cadena = "Lambdas tipo funciones";
System.out.println("Número de caracteres es : " + tamanioCadena.apply(cadena));

Proveedores

Las expresiones Lambda de este tipo no tiene parámetros de entrada, pero si devuelven un resultado, utilizan la interface Supplier<T>.

Veamos un ejemplo sencillo, que básicamente obtiene la cadena enviada a la interface funcional, a través de una expresión Lambda tipo proveedor.

Supplier<String> cadena = () -> "Ejemplo de Proveedor";
System.out.println(cadena.get());

Un ejemplo un poco más detallado, primero creamos una clase Persona.

public class Persona {
	private String nombre;
	private String apellido;
	private String direccion;
	
	
	public Persona(String nombre, String apellido, String direccion) {
		this.nombre = nombre;
		this.apellido = apellido;
		this.direccion = direccion;
	}
	public String getNombre() {
		return nombre;
	}
	public void setNombre(String nombre) {
		this.nombre = nombre;
	}
	public String getApellido() {
		return apellido;
	}
	public void setApellido(String apellido) {
		this.apellido = apellido;
	}
	public String getDireccion() {
		return direccion;
	}
	public void setDireccion(String direccion) {
		this.direccion = direccion;
	}
}

Se implementa la expresión Lambda que es de tipo proveedor utilizando la clase Supplier.

package com.ecodeup.lambda;

import java.util.function.Supplier;

public class TestLambda {

	public static void main(String[] args) {
               //se crea un proveedor de tipo Persona, el cual obtiene una persona
		Supplier<Persona> supplier = TestLambda::llenarPersona;
               //obtiene desde el proveedor la persona y la asigna a per
		Persona per = supplier.get();
               // imprime el nombre
		System.out.println(per.getNombre());
	}
	// asigna los nombres y dirección a la persona
	public static Persona llenarPersona(){
		return new Persona("Pablo", "Andrade", "Loja");
	}
}

Consumidor

Utilizan la interfaz Consumer<T>, tienen un sólo argumento de entrada y no devuelven ningún valor, en este ejemplo se usa la misma clase Persona que se utilizó en el ejemplo de tipo proveedor.

Consumer<Persona> persona = (p) -> System.out.println("Hola, " + p.getNombre());
persona.accept(new Persona("Jorge", "Valladares","Quito"));

REFERENCIA A MÉTODOS

Con esta funcionalidad no sólo se puede utilizar expresiones lambda sino que se puede hacer referencia a los métodos del objeto utilizando el operador ::, existen 3 tipos:

Nota: Para estos ejemplos se utilizó la clase Usuario que se encuentra en la parte de Resumen expresiones Lambdas que viene luego de este tema.

Métodos estáticos

package com.ecodeup.lambdas;

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

public class TestReferenciaMetodos {

	public static void main(String[] args) {
		List names = new ArrayList();		
	      names.add("Andrea");
	      names.add("Luisa");
	      names.add("Diego");
	      names.add("Paúl");
	      names.add("Dario");			
	      names.forEach(System.out::println);
	}

}

En este ejemplo se utiliza el método System.out::println, como referencia a métodos estáticos.

Métodos de instancia

//recibe un objeto Usuario y devuelve la impresión de sus propiedades.
	      Function<Usuario, String> ftoString= Usuario::toString;
	      System.out.println(ftoString.apply(new Usuario("Santiago","Pardo",18,new Direccion("Nueva Dirección"))));

En este ejemplo se utiliza el método toString() que fue redefinido en la clase Usuario.

Referencia a mensajes

// referencia a mensajes
		LinkedList<Integer> lista = new LinkedList<Integer>(Arrays.asList(1, 2, 3));
		Supplier<Integer> funcion3 = lista::removeLast;
		System.out.println(funcion3.get()); // 3
		lista.forEach(System.out::println);

En este ejemplo se utiliza El método removeLast para eliminar el último elemento de la lista por último, se imprime la lista.

Referencia a constructores

//referencia a constructores
		Supplier<Usuario> usu= Usuario::new;
		//Construye un objeto de tipo usuario que es devuelto por método get();
		Usuario usuario=usu.get();

En este ejemplo se utiliza el operador  new para crea una referencia a un objeto de tipo Usuario.

Resumen expresiones Lambdas

package com.ecodeup.lambdas;

public class Usuario {

	private String nombre;
	private String apellido;
	private int edad;
	private Direccion dir;

	public Usuario(String nombre, String apellido, int edad, Direccion dir) {
		this.nombre = nombre;
		this.apellido = apellido;
		this.edad = edad;
		this.dir=dir;
	}
		
	public Usuario() {
		
	}

	public String getNombre() {
		return nombre;
	}

	public void setNombre(String nombre) {
		this.nombre = nombre;
	}

	public String getApellido() {
		return apellido;
	}

	public void setApellido(String apellido) {
		this.apellido = apellido;
	}

	public int getEdad() {
		return edad;
	}

	public void setEdad(int edad) {
		this.edad = edad;
	}



	public Direccion getDir() {
		return dir;
	}
	public void setDir(Direccion dir) {
		this.dir = dir;
	}
	@Override
	public String toString() {
		return this.nombre+" "+this.apellido+" "+this.edad+" "+this.dir.getNombre();
	}
}
package com.ecodeup.lambdas;

public class Direccion {
	private String nombre;

	
	public Direccion(String nombre) {
		this.nombre = nombre;
	}

	public Direccion() {
		
	}

	public String getNombre() {
		return nombre;
	}

	public void setNombre(String nombre) {
		this.nombre = nombre;
	}
	
	
	
}
package com.ecodeup.lambdas;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

public class TestLambdas {

	public static void main(String[] args) {
		List< Usuario> names = new ArrayList<Usuario>();		
	      names.add(new Usuario("Elivar", "Oswaldo", 10, new Direccion("San Pedro")));
	      names.add(new Usuario("Antonio", "Carrion", 15, new Direccion("bellavista")));
	      names.add(new Usuario("Juan", "Andrade", 12,new Direccion("San Pedro1")));
	      names.add(new Usuario("Luis", "Aguilar", 17,new Direccion("San Pedro2")));
	      names.add(new Usuario("Fidel", "Narvaez", 8,new Direccion("San Pedro3")));
	      names.add(new Usuario("Paul", "Guevara", 5,new Direccion("San Pedro4")));
	      //Predicate<Tipo>
	      //predicado: obtiene le número de usuarios con edad mayor >12 años
	      System.out.println("Ejemplo de predicado:");
	      System.out.println("Usuarios mayores a 12 años: "+names.stream().filter(x->x.getEdad()>12 ).count());
	      
	      //Function<T,R>
	      //Funcion: Obtiene la dirección que corresponde al usuario de la posición 0 de la lista
	      System.out.println("\nEjemplo de función:");
	      Function<Usuario,Direccion> funDireccion= v->v.getDir();
	      System.out.println(funDireccion.apply(names.get(0)).getNombre());
	      
	     // Consumer<Tipo>
	      //ejemplo consumidor: Actualiza el apellido del usuario de la posición 0 de la lista
	      System.out.println("\nEjemplo de consumidor:");
	      Consumer<Usuario> cambiaApellido = u->u.setApellido("Aguirre");
	      cambiaApellido.accept(names.get(0));
	       
	      //imprime usuario actualizado
	      names.forEach(System.out::println);
	      
	      //Supplier<Tipo>
	      //proveedor: Crea un nuevo usuario y lo imprime con la función get
	      System.out.println("\nEjemplo de proveedor:");
	      Supplier<Usuario> u=()->new Usuario("Augusto", "Velez", 5,new Direccion("Cayambe"));
	      System.out.println(u.get());	      
	}
}

Básicamente estos son los fundamentos para aprender las expresiones Lambda en Java, si quieres obtener información más a fondo puedes consultar la página oficial Lambda Expressions o también Paquete de Interfaces Funcionales.

Espero que este tutorial te haya servido, házmelo saber en los comentarios, nos vemos en la próxima entrada.

Mi nombre es Elivar Largo, Developer Full Stack, blogger y emprendedor. Trabajo y comparto conocimientos sobre las siguientes tecnologías: Spring Boot, Angular, Flutter. Contacto: elargor@gmail.com.

6 comentarios en «Entendiendo paso a paso las expresiones Lambda en Java 8»

Deja una respuesta

Tu dirección de correo electrónico no será publicada.

7  +  3  =  

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.