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