Hoy hablaremos de una característica fundamental del lenguaje Java que apareció desde la versión 8: las expresiones lambda.
Las cuales no son otra cosa que funciones que no necesitan pertenecer a alguna clase previamente definida, es decir, son anónimas.
Características:
- Vienen de la programación funcional.
- Son funciones anónimas.
- Se escriben en línea.
- Pueden o no recibir cero o más argumentos.
- Pueden o no retornar algún valor.
- Pueden consumir métodos de otras clases y objetos.
- Pueden pasar como argumentos en los métodos.
- Solo pueden acceder a variables del ámbito al que pertenecen.
La sintaxis básica es la siguiente:
() => {cuerpo-lambda} (param) => {cuerpo-lambda} (param1, param2) => {cuerpo-lambda} (param1, param2, paramN) => {cuerpo-lambda}
Miremos unos ejemplos:
Sin parámetros:
() -> System.out.println("Hola desde una lambda sin parámetros");
Con un parámetro:
Predicate<Integer> esPar = x -> x%2 ==0;
En este ejemplo usamos la interface ``Predicate``.
Existen 4 principales expresiones lambda (functional interfaces):
- Consumidores (Consumer), los consumidores.
- Proveedores (Supplier), los proveedores.
- Funciones(Function), las funciones: unarios y binarios
- Predicados(Predicate), los predicados.
Para poder usarlos es necesario importar el paquete:
import java.util.function.*;
O de esta forma más correcta:
import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier;
¿Para qué nos sirven estas 4 expresiones lambda (funtional interfaces)?
- Consumer: Puede o no recibir algún valor y no devolver nada.
- Function: Puede tomar dos valores de distinto tipo y devolver un valor en específico.
- Predicate: Puede tomar valores y devolver un valor booleano.
- Supplier: No recibe valores, pero devuelve un valor.
Veamos un ejemplo con el ``Consumer``, que es una interface que que acepta un tipo determinado y realiza una operación, esto sin devolver nada. Tenemos una clase llamada TestLambdas.java`y dentro de esta tenemos un bloque que contiene una lista de tipo String y queremos recorrerla con un "for each":
package com.inforhomex; import java.util.ArrayList; import java.util.List; public class TestLambdas{ public static void main(String[] args){ List<String> nombres = new ArrayList<String>(); nombres.add("Tomas"); nombres.add("Azucena"); nombres.add("Leopoldo"); nombres.add("Miguel"); nombres.add("Berenice"); nombres.add("Ricardo"); /*** Recorrer con "for each" ****/ for(String nombre: nombres){ System.out.printf("Hola, %s\n", nombre); } } }
¿Cómo usaríamos la interface ``Consumer`` en este ejmplo?
package com.inforhomex; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; public class TestLambdas{ public static void main(String[] args){ List<String> nombres = new ArrayList<String>(); nombres.add("Tomas"); nombres.add("Azucena"); nombres.add("Leopoldo"); nombres.add("Miguel"); nombres.add("Berenice"); nombres.add("Ricardo"); System.out.println("\n--- Usando Consumer ---"); Consumer<String> consumir = (n) -> { System.out.printf("Hola, %s\n",n); }; nombres.forEach(consumir); } }
La interface ``Consumer`` nos ha servido para mostrar "de una forma más limpia" los items de la lista tipo String.
Usando la interface ``Function``. Esta interface recibe un valor y lo devuelve transformado. Miremos este ejemplo:
package com.inforhomex; import java.util.ArrayList; import java.util.List; import java.util.function.Function; public class TestLambdas { public static void main(String[] args) { List<String> nombres = new ArrayList<String>(); nombres.add("Tomas"); nombres.add("Azucena"); nombres.add("Leopoldo"); nombres.add("Miguel"); nombres.add("Berenice"); nombres.add("Ricardo"); System.out.println("\n--- Usando Function ---"); Function<String, String> aMayusculas = (n) -> n.toUpperCase(); nombres.stream() .map(aMayusculas) .forEach(n -> System.out.println("En mayúsculas: " + n)); } }
Nos devuelve cada uno de los nombres en mayúscula.
Usando la interface ``Predicate``. Esta interface recibe un parámetro y nos devuelve una salida en true o false. Miremos el ejemplo:
package com.inforhomex; import java.util.ArrayList; import java.util.List; import java.util.function.Predicate; public class TestLambdas { public static void main(String[] args) { List<String> nombres = new ArrayList<String>(); nombres.add("Tomas"); nombres.add("Azucena"); nombres.add("Leopoldo"); nombres.add("Miguel"); nombres.add("Berenice"); nombres.add("Ricardo"); System.out.println("\n--- Usando Predicate ---"); Predicate<String> empiezaConA = (n) -> n.startsWith("A"); nombres.stream() .filter(empiezaConA) .forEach(n -> System.out.println("Nombre con A: " + n)); } }
Solo mostrará los nombres que empiezan con la letra 'A'.
Usando la interface ``Supplier``. Está interface no recibe ningún valor, pero si devolverá algo.
package com.inforhomex; import java.util.ArrayList; import java.util.List; import java.util.function.Supplier; public class TestLambdas { public static void main(String[] args) { List<String> nombres = new ArrayList<String>(); nombres.add("Tomas"); nombres.add("Azucena"); nombres.add("Leopoldo"); nombres.add("Miguel"); nombres.add("Berenice"); nombres.add("Ricardo"); System.out.println("\n--- Usando Supplier ---"); Supplier<String> proveedor = () -> "Michael"; System.out.println(proveedor.get()); } }
En este caso devolverá un nombre.
Código completo:
TestLambdas.java
package com.inforhomex; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; public class TestLambdas { public static void main(String[] args) { List<String> nombres = new ArrayList<String>(); nombres.add("Tomas"); nombres.add("Azucena"); nombres.add("Leopoldo"); nombres.add("Miguel"); nombres.add("Berenice"); nombres.add("Ricardo"); System.out.println("\n--- Usando Consumer ---"); Consumer<String> consumir = (n) -> System.out.printf("Hola, %s\n", n); nombres.forEach(consumir); System.out.println("\n--- Usando Predicate ---"); Predicate<String> empiezaConA = (n) -> n.startsWith("A"); nombres.stream() .filter(empiezaConA) .forEach(n -> System.out.println("Nombre con A: " + n)); System.out.println("\n--- Usando Function ---"); Function<String, String> aMayusculas = (n) -> n.toUpperCase(); nombres.stream() .map(aMayusculas) .forEach(n -> System.out.println("En mayúsculas: " + n)); System.out.println("\n--- Usando Supplier ---"); Supplier<String> proveedor = () -> "Michael"; System.out.println(proveedor.get()); } }
Este patrón es muy útil cuando trabajas con Streams y colecciones, porque te permite filtrar, transformar y consumir datos de manera declarativa.
Las expresiones lambdas vuelven a Java un lenguaje más moderno implementando conceptos de la programación funcional.
Enlaces:
https://www.arteco-consulting.com/articulos/introduccion-java-lambda/https://codemonkeyjunior.wordpress.com/2019/04/09/java-lambdas/
https://codemonkeyjunior.wordpress.com/2018/04/23/un-vistazo-a-lambdas-java-8/
https://codemonkeyjunior.wordpress.com/2019/03/19/java-8-java-util-function/
https://asjordi.dev/blog/pasar-funciones-lambda-como-argumentos-de-metodos-en-java/

Comentarios
Publicar un comentario