Interfaces funcionales

Predicate, Function, Consumer y Supplier

¿Qué es una interfaz funcional?

Una interfaz funcional es cualquier interfaz con exactamente un método abstracto. La anotación @FunctionalInterface es opcional pero recomendable: hace que el compilador verifique la restricción. Las lambdas son instancias de interfaces funcionales.

// Una interfaz funcional tiene exactamente UN método abstracto
@FunctionalInterface
public interface MiOperacion {
    int ejecutar(int a, int b); // único método abstracto

    // Puede tener default methods y métodos estáticos sin problema
    default String descripcion() { return "operación personalizada"; }
}

// Se puede usar con lambda directamente
MiOperacion suma = (a, b) -> a + b;
MiOperacion producto = (a, b) -> a * b;
System.out.println(suma.ejecutar(3, 4)); // 7

Predicate<T>

Representa una condición booleana sobre un valor de tipo T. Método abstracto: boolean test(T t). Soporta composición lógica mediante and, or y negate.

import java.util.function.Predicate;

Predicate<String> esLargo = s -> s.length() > 5;
Predicate<String> empiezaConA = s -> s.startsWith("A");

// Composición de predicados
Predicate<String> ambos = esLargo.and(empiezaConA);
Predicate<String> alguno = esLargo.or(empiezaConA);
Predicate<String> negado = esLargo.negate();

System.out.println(esLargo.test("Hola"));      // false
System.out.println(esLargo.test("Hola mundo")); // true
System.out.println(ambos.test("Astronauta"));   // true

Function<T, R>

Transforma un valor de tipo T en un valor de tipo R. Método abstracto: R apply(T t). Permite encadenar transformaciones con andThen y compose.

import java.util.function.Function;

Function<String, Integer> longitud = String::length;
Function<Integer, String> toStr = Object::toString;

// Composición: andThen aplica la segunda función al resultado de la primera
Function<String, String> longitudComoTexto = longitud.andThen(toStr);
System.out.println(longitudComoTexto.apply("Java")); // "4"

// compose: aplica la función argumento ANTES que la actual
Function<String, String> enMayus = String::toUpperCase;
Function<String, String> pipeline = longitud.andThen(toStr).compose(enMayus);
// enMayus -> longitud -> toStr

Consumer<T> y Supplier<T>

Consumer representa una operación con efecto secundario que no devuelve nada (void accept(T t)). Supplier es la operación inversa: no recibe nada y produce un valor (T get()) — útil para evaluación perezosa y fábricas.

import java.util.function.Consumer;
import java.util.function.Supplier;

// Consumer<T>: recibe T, no devuelve nada (efecto secundario)
Consumer<String> imprimir = System.out::println;
Consumer<String> guardar = s -> baseDeDatos.guardar(s);

// Encadenamiento: andThen
Consumer<String> imprimirYGuardar = imprimir.andThen(guardar);
imprimirYGuardar.accept("nuevo registro");

// Supplier<T>: no recibe nada, devuelve T (fábrica / lazy evaluation)
Supplier<List<String>> listaVacia = ArrayList::new;
Supplier<LocalDate> hoy = LocalDate::now;

List<String> lista = listaVacia.get();

Variantes con dos parámetros

import java.util.function.*;

// BiFunction<T, U, R>: dos argumentos, un resultado
BiFunction<String, Integer, String> repetir = (s, n) -> s.repeat(n);
System.out.println(repetir.apply("ab", 3)); // "ababab"

// UnaryOperator<T>: Function<T,T> (entrada y salida del mismo tipo)
UnaryOperator<String> trim = String::trim;

// BinaryOperator<T>: BiFunction<T,T,T>
BinaryOperator<Integer> max = Integer::max;
System.out.println(max.apply(3, 7)); // 7
Resumen de las cuatro: Predicate → pregunta (boolean), Function → transforma (T → R), Consumer → actúa sin devolver nada, Supplier → produce sin recibir nada. Todas componen entre sí y son la base del Stream API.

Siguiente apartado → Stream API: procesamiento declarativo de colecciones

Índice de la sección

Índice del curso