Introducción a la programación funcional en Java
Durante mucho tiempo, Java se asoció exclusivamente con la programación orientada a objetos (POO). Sin embargo, con el lanzamiento de Java 8 en 2014, el lenguaje dio un gran salto hacia el paradigma funcional. Las expresiones lambda, las interfaces funcionales y Stream API cambiaron radicalmente el enfoque de escritura de código, haciéndolo más conciso, legible y menos propenso a errores.
La programación funcional en Java no reemplaza a la POO, sino que es un poderoso complemento. Permite escribir código declarativo, donde describes qué se debe hacer, no cómo. En este artículo, analizaremos los conceptos clave que te ayudarán a dominar el estilo funcional en Java y mostraremos ejemplos prácticos.
1. Expresiones lambda e interfaces funcionales
La base de la programación funcional en Java son las expresiones lambda — una forma compacta de implementar funciones anónimas. Sintácticamente, una lambda consta de una lista de parámetros, una flecha (->) y un cuerpo:
(parámetros) -> { cuerpo }Las lambdas siempre trabajan junto con interfaces funcionales. Una interfaz funcional es una interfaz que contiene exactamente un método abstracto (por ejemplo, Runnable, Comparator, Predicate).
Ejemplo práctico: reemplazo de una clase anónima
Comparemos el enfoque antiguo (POO) con el nuevo (funcional):
// Forma antigua (clase anónima)Runnable taskOld = new Runnable() { @Override public void run() { System.out.println("¡Hola desde una clase anónima!"); }};
// Forma nueva (lambda)Runnable taskNew = () -> System.out.println("¡Hola desde una lambda!");
// EjecucióntaskNew.run();Como puedes ver, la lambda redujo el código de 6 líneas a 1, manteniendo toda la funcionalidad.
Interfaces funcionales incorporadas (paquete java.util.function)
Java proporciona un rico conjunto de interfaces incorporadas. Aquí están las cuatro principales:
Predicate<T>— recibe un objeto, devuelve unboolean. Se usa para filtrar.Function<T, R>— recibe un objeto de un tipo, devuelve un objeto de otro tipo. Para transformaciones.Consumer<T>— recibe un objeto, no devuelve nada (void). Para efectos secundarios (imprimir en pantalla).Supplier<T>— no recibe nada, devuelve un objeto. Para generación perezosa de datos.
Ejemplo con Predicate:
import java.util.function.Predicate;
public class Main { public static void main(String[] args) { Predicate<Integer> isEven = n -> n % 2 == 0; System.out.println(isEven.test(10)); // true System.out.println(isEven.test(7)); // false }}2. Stream API: trabajo con colecciones en estilo funcional
Stream API es la joya de la programación funcional en Java. Un flujo (Stream) es una secuencia de elementos que admite operaciones secuenciales y paralelas. La principal ventaja: no modificas los datos originales, sino que creas un nuevo resultado.
Stream API se basa en tres etapas:
- Creación del flujo (desde una colección, array o generador).
- Operaciones intermedias (filtrado, ordenación, transformación). Devuelven un nuevo Stream.
- Operación terminal (recopilación del resultado, conteo, búsqueda). Finaliza el flujo.
Ejemplo: filtrado y transformación de una lista
Supongamos que tenemos una lista de cadenas y queremos obtener solo aquellas que tengan más de 3 caracteres, convertirlas a mayúsculas y ordenarlas.
import java.u