Programación funcional en Java: nivel avanzado
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, siendo un lenguaje orientado a objetos, recibió con Java 8 potentes herramientas para PF: expresiones lambda, Stream API e interfaces funcionales. A nivel avanzado, estas capacidades permiten escribir código más conciso, seguro y expresivo, especialmente al trabajar con colecciones, operaciones asíncronas y manejo de errores.
En este artículo exploraremos técnicas avanzadas de programación funcional en Java: composición de funciones, mónadas (Optional y CompletableFuture), Stream API con procesamiento paralelo y currying.
1. Composición de funciones e interfaces funcionales
En Java, las interfaces funcionales son interfaces con un único método abstracto (SAM). Las más utilizadas son: Function<T, R>, Predicate<T>, Consumer<T> y Supplier<T>. El uso avanzado incluye la composición de funciones mediante los métodos andThen() y compose().
Ejemplo: cadena de transformaciones
import java.util.function.Function;
public class FunctionComposition { public static void main(String[] args) { // Función: string -> su longitud Function<String, Integer> lengthFunction = String::length; // Función: número -> su cuadrado Function<Integer, Integer> squareFunction = x -> x * x;
// Composición: string -> longitud -> cuadrado de la longitud Function<String, Integer> composedFunction = lengthFunction.andThen(squareFunction);
String text = "Hello, Java!"; int result = composedFunction.apply(text); // 12 * 12 = 144 System.out.println("Resultado: " + result); // 144 }}El método andThen() aplica primero la primera función y luego la segunda. compose() hace lo contrario: primero aplica la función pasada y luego la primera. Esto permite construir cadenas de transformaciones flexibles y reutilizables.
Ejemplo práctico: filtrado y transformación de una lista
import java.util.List;import java.util.function.Function;import java.util.function.Predicate;import java.util.stream.Collectors;
public class PredicateAndFunction { public static void main(String[] args) { List<String> names = List.of("Alice", "Bob", "Charlie", "David", "Eve");
Predicate<String> startsWithA = s -> s.startsWith("A"); Function<String, String> toUpperCase = String::toUpperCase;
List<String> result = names.stream() .filter(startsWithA) .map(toUpperCase) .collect(Collectors.toList());
System.out.println(result); // [ALICE] }}2. Mónadas en Java: Optional y CompletableFuture
Una mónada es un patrón que encapsula un valor y proporciona operaciones para transformarlo sin desempaquetarlo explícitamente. En Java, las mónadas se implementan a través de Optional (para manejar null) y CompletableFuture (para operaciones asíncronas).
Optional como mónada
Los métodos map(), flatMap() y filter() permiten trabajar en cadena con la posible ausencia de un valor.
import java.util.Optional;
public class OptionalMonad { public static void main(String[] args) { // Creamos un Optional con un valor no vacío Optional<String> optional = Optional.of("Hello");
// Cadena de transformaciones String result = optional .filter(s -> s.length() > 3) // filtro .m