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>recibeTy devuelvebooleancontest.Function<T, R>transformaTenRconapply.Consumer<T>recibeTy no devuelve resultado, medianteaccept.Supplier<T>no recibe datos y produceTconget.
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.