Programación funcional en Java: de lambdas a Stream API
La programación funcional (PF) es un paradigma en el que los cálculos se representan como la evaluación de funciones matemáticas, evitando el cambio de estado y la mutación de datos. Durante mucho tiempo, Java siguió siendo un lenguaje estrictamente orientado a objetos, pero con el lanzamiento de Java 8 (año 2014) dio un gran paso hacia la PF, añadiendo expresiones lambda, interfaces funcionales y Stream API. Hoy en día, estas herramientas son indispensables para escribir código conciso, legible y seguro para la concurrencia.
En este artículo, analizaremos los conceptos clave de la programación funcional en Java: desde los fundamentos de las lambdas hasta el procesamiento avanzado de colecciones con Stream API. El material está dirigido a desarrolladores que ya conocen la sintaxis básica de Java, pero quieren dominar los enfoques modernos.
1. Expresiones lambda e interfaces funcionales
Una expresión lambda es una función anónima (un método sin nombre) que se puede pasar como argumento o almacenar en una variable. En Java, una lambda implementa una interfaz funcional — una interfaz con exactamente un método abstracto (SAM — Single Abstract Method).
Sintaxis:
(parámetros) -> { cuerpo }Ejemplo: en lugar de crear una clase anónima para un comparador:
// Forma antigua (Java 7)Comparator<String> comparator = new Comparator<String>() { @Override public int compare(String s1, String s2) { return s1.length() - s2.length(); }};
// Forma nueva (Java 8+)Comparator<String> comparatorLambda = (s1, s2) -> s1.length() - s2.length();Interfaces funcionales principales en Java (paquete java.util.function):
Predicate<T>— verifica una condición (métodotest).Function<T, R>— transforma un objeto de tipo T en R (métodoapply).Consumer<T>— realiza una acción sobre un objeto (métodoaccept).Supplier<T>— proporciona un objeto sin parámetros de entrada (métodoget).
Ejemplo con Predicate:
Predicate<Integer> isEven = n -> n % 2 == 0;System.out.println(isEven.test(4)); // trueSystem.out.println(isEven.test(5)); // falseReferencias a métodos (Method References) — son una forma abreviada de escribir una lambda cuando el cuerpo consiste en una sola llamada a un método:
// Lambdalist.forEach(item -> System.out.println(item));// Referencia a métodolist.forEach(System.out::println);2. Stream API: operaciones básicas
Stream API es una herramienta poderosa para procesar secuencias de datos (colecciones, arrays, archivos) de forma declarativa. Un Stream no almacena datos, solo describe un pipeline de operaciones. Los datos no se modifican — cada paso crea un nuevo stream.
Creación de un stream:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");Stream<String> stream = names.stream(); // secuencialStream<String> parallelStream = names.parallelStream(); // paraleloOperaciones intermedias (devuelven un nuevo Stream):
filter(Predicate)— selecciona elementos según una condición.map(Function)— transforma cada elemento.sorted()/sorted(Comparator)— ordenación.distinct()— elimina duplicados.limit(n)— limita la cantidad de elementos.
Operaciones terminales (finalizan el stream, devuelven un resultado):
collect(Collectors.toList())— recoge en una colección.forEach(Con