Programación funcional en Java: de la A a la Z
La programación funcional (PF) es un paradigma en el que los cálculos se consideran como la evaluación de funciones matemáticas, evitando cambios de estado y datos mutables. Java, tradicionalmente un lenguaje imperativo, a partir de la versión 8 ha incorporado activamente elementos de PF. Esto permite escribir código más conciso, predecible y fácil de probar, especialmente al trabajar con colecciones y operaciones asíncronas.
En este artículo, analizaremos los conceptos clave de PF en Java: desde expresiones lambda hasta técnicas avanzadas con Stream API y Optional. Aprenderá cómo aplicar estas herramientas en la práctica para que su código sea más limpio y eficiente.
1. Fundamentos: Expresiones lambda e Interfaces funcionales
Expresión lambda es una forma abreviada de escribir un método anónimo (función). Permite pasar comportamiento como datos. Sintaxis: (parámetros) -> cuerpo.
Interfaz funcional es una interfaz con un único método abstracto (SAM — Single Abstract Method). Estas interfaces pueden implementarse mediante lambdas. Interfaces integradas clave en java.util.function:
Predicate<T>— recibe un objeto T, devuelve boolean (métodotest).Function<T, R>— recibe T, devuelve R (métodoapply).Consumer<T>— recibe T, no devuelve nada (métodoaccept).Supplier<T>— no recibe nada, devuelve T (métodoget).UnaryOperator<T>— caso especial de Function, donde T y R son del mismo tipo.
Ejemplo: Ordenar una lista con Comparator (interfaz funcional)
import java.util.*;
public class LambdaExample { public static void main(String[] args) { List<String> names = Arrays.asList("Iván", "Ana", "Pedro", "María");
// Sin lambda (clase anónima) Collections.sort(names, new Comparator<String>() { @Override public int compare(String a, String b) { return a.length() - b.length(); } });
// Con lambda Collections.sort(names, (a, b) -> a.length() - b.length());
// Aún más corto: usando Comparator.comparingInt names.sort(Comparator.comparingInt(String::length));
System.out.println(names); // [Ana, Pedro, Iván, María] }}Ejemplo: Uso de Predicate para filtrar
import java.util.function.Predicate;
public class PredicateExample { public static void main(String[] args) { Predicate<Integer> isEven = n -> n % 2 == 0; Predicate<Integer> isPositive = n -> n > 0;
System.out.println(isEven.test(4)); // true System.out.println(isEven.and(isPositive).test(-2)); // false (no es positivo) System.out.println(isEven.or(isPositive).test(-3)); // true (no es par, pero ¿es positivo? no, -3 no es positivo) }}2. Stream API: Potente herramienta de procesamiento de datos
Stream API es una secuencia de elementos que admite operaciones paralelas y secuenciales. Stream no almacena datos, sino que los transforma. Las operaciones clave se dividen en intermedias (devuelven un nuevo Stream) y terminales (finalizan el procesamiento y devuelven un resultado o efecto secundario).
Operaciones principales de Stream API:
- Intermedias:
filter,map,flatMap,sorted,distinct,peek,limit,skip. - Terminales:
collec