Una interfaz funcional es una interfaz con un único método abstracto que puede implementarse mediante una lambda.
En el artículo anterior aprendimos a escribir flechas (->) para expresar comportamientos breves.
Pero, si yo escribo:
s -> s.length()
¿De qué tipo es eso? ¿En qué variable lo guardo?
Antes de Java 8, teníamos que inventarnos interfaces propias como Calculadora o Imprimible. Pero Java 8 dijo: “Basta de reinventar la rueda. Vamos a crear un estándar”.
Así nació el paquete java.util.function.
Aquí viven varias interfaces predefinidas que cubren muchos casos habituales. Nos centraremos en cuatro: Predicate, Consumer, Function y Supplier.
Predicate <T>
El Predicado sirve para evaluar una condición. Toma un objeto y dice SI o NO (true/false).
- Recibe: Un objeto
T. - Devuelve:
boolean. - Método abstracto:
test(T t).
// Definición: Un predicado que decide si un String es largo
Predicate<String> esLargo = texto -> texto.length() > 5;
// Uso:
boolean resultado = esLargo.test("Hola Mundo"); // true
boolean resultado2 = esLargo.test("Java"); // false
Uso Real: Es el rey del método .filter() en los Streams (filtrar listas).
Consumer <T>
El Consumidor sirve para ejecutar una acción sobre un objeto. Se traga el objeto, hace algo con él (imprimirlo, guardarlo en BD, enviarlo) y no devuelve nada.
- Recibe: Un objeto
T. - Devuelve:
void(Nada). - Método abstracto:
accept(T t).
// Definición: Un consumidor que imprime en mayúsculas
Consumer<String> gritador = texto -> System.out.println(texto.toUpperCase());
// Uso:
gritador.accept("hola"); // Imprime: "HOLA"
Uso Real: Es el rey del método .forEach() (recorrer listas).
Function <T, R>
La Función sirve para convertir una cosa en otra.
Toma una materia prima (T) y devuelve un producto procesado (R).
- Recibe: Un objeto
T. - Devuelve: Un objeto
R(Resultado). - Método abstracto:
apply(T t).
// Definición: Transforma un String (T) en su longitud Integer (R)
Function<String, Integer> longitud = texto -> texto.length();
// Uso:
Integer largo = longitud.apply("Java"); // 4
Uso Real: Es el rey del método .map() (transformar datos).
Supplier <T>
El Proveedor (o Supplier) sirve para generar valores de la nada. No recibe nada, pero te entrega algo. Es como una tubería de la que salen objetos.
- Recibe: Nada
(). - Devuelve: Un objeto
T. - Método abstracto:
get().
// Definición: Genera un número aleatorio
Supplier<Double> aleatorio = () -> Math.random();
// Uso:
Double n = aleatorio.get();
Uso Real: Se usa mucho en inicialización perezosa (Lazy) y en Optional.orElseGet() (crear un objeto por defecto solo si hace falta).
Variantes “bi” y primitivas
A veces necesitamos funciones que reciban dos argumentos. Java añade el prefijo Bi.
BiFunction<T, U, R>: Recibe T y U, devuelve R. (Ej: Sumar dos números).BiConsumer<T, U>: Recibe T y U, no devuelve nada. (Ej: Poner clave-valor en un Mapa).BiPredicate<T, U>: Recibe T y U, devuelve boolean.
Y para evitar el coste de los Wrappers (Integer), hay versiones para primitivos: IntPredicate, LongFunction, etc.