Saltar al contenido principal

Lambdas

A veces una operación necesita recibir no solo datos, sino un comportamiento: una regla de validación, una transformación o una acción. Una lambda expresa ese comportamiento en el contexto de una interfaz funcional.

Interfaz funcional​

Tiene un único método abstracto:

@FunctionalInterface
interface Operacion {
int aplicar(int a, int b);
}

Operacion suma = (a, b) -> a + b;
System.out.println(suma.aplicar(3, 4));

La lambda no es una forma corta de cualquier método: necesita un tipo objetivo cuyo único método abstracto determine parámetros y retorno.

Formas de sintaxis​

Supplier<String> saludo = () -> "Hola";             // cero parámetros
Predicate<String> largo = texto -> texto.length() > 5; // uno
Operacion resta = (a, b) -> a - b; // varios

Operacion divisionSegura = (a, b) -> {
if (b == 0) return 0;
return a / b;
};

Una expresión devuelve su resultado implícitamente. Un bloque usa llaves y return cuando corresponde. Normalmente Java infiere los tipos.

Referencias a métodos​

Cuando una lambda se limita a llamar a un método existente, puede escribirse de forma más compacta. Por ejemplo, esta transformación:

Function<String, String> limpiarLambda = texto -> texto.trim();

equivale, en un contexto compatible, a una referencia al método de instancia que se ejecutará sobre el parámetro:

Function<String, String> limpiarReferencia = String::trim;

También podemos referirnos al método de un objeto concreto. Estas dos acciones son equivalentes:

Consumer<String> mostrarLambda = texto -> System.out.println(texto);
Consumer<String> mostrarReferencia = System.out::println;

La referencia sigue necesitando un contexto de interfaz funcional: Function determina qué recibe y devuelve String::trim, mientras que Consumer define el contrato de System.out::println.

Interfaces estándar​

  • Predicate<T> recibe T y devuelve boolean con test.
  • Function<T, R> transforma T en R con apply.
  • Consumer<T> recibe T y no devuelve resultado, mediante accept.
  • Supplier<T> no recibe datos y produce T con get.
Predicate<Integer> positivo = numero -> numero > 0;
Function<String, Integer> longitud = texto -> texto.length();
Consumer<String> mostrar = texto -> System.out.println(texto);
Supplier<String> crearCodigo = () -> "T-01";

Lambdas sin Stream​

List<String> nombres = new ArrayList<>(List.of("Leo", "Ana", "Marta"));
nombres.sort((a, b) -> a.compareToIgnoreCase(b));
nombres.forEach(nombre -> System.out.println(nombre));
nombres.removeIf(nombre -> nombre.length() < 4);

sort recibe un Comparator, forEach un Consumer y removeIf un Predicate. La colección sigue siendo la que almacena y modifica los datos.

Variables capturadas​

int minimo = 4;
Predicate<String> suficiente = texto -> texto.length() >= minimo;

La lambda captura minimo. Esa variable debe ser final o effectively final: no reasignada después de inicializarse. Esto hace estable el valor capturado.

Ejemplo completo​

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 LambdasDemo {
public static void main(String[] args) {
int minimo = 4;
Predicate<String> valida = texto -> texto.length() >= minimo;
Function<String, String> normaliza = texto -> texto.trim().toUpperCase();
Consumer<String> muestra = texto -> System.out.println("- " + texto);
Supplier<List<String>> creaLista = () -> new ArrayList<>();

List<String> datos = creaLista.get();
datos.add(" java ");
datos.add("sql");
datos.sort((a, b) -> a.trim().compareToIgnoreCase(b.trim()));

for (String dato : datos) {
String limpio = normaliza.apply(dato);
if (valida.test(limpio)) muestra.accept(limpio);
}
}
}

Errores habituales​

  • Escribir una lambda sin contexto de interfaz funcional.
  • Confundir el cuerpo de bloque con el de expresión.
  • Intentar reasignar una variable capturada.
  • Usar una lambda larga que oculta la intención.
  • Introducir efectos secundarios en una función que debería transformar.

Practica: preparar productos​

Enunciado y solución​

Ordena nombres sin distinguir mayúsculas, elimina los que estén vacíos y muestra los restantes con un prefijo. Usa lambdas, pero no Stream API.

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

public class ProductosLambdaDemo {
public static void main(String[] args) {
List<String> productos = new ArrayList<>(List.of("Ratón", "", "teclado"));
productos.removeIf(texto -> texto.isBlank());
productos.sort((a, b) -> a.compareToIgnoreCase(b));
productos.forEach(texto -> System.out.println("Producto: " + texto));
}
}

Cada lambda encaja en un contrato concreto. En JV3-08 las utilizaremos para describir operaciones dentro de pipelines.