
Entidades de java.util.functional¶
Una interfaz funcional es una interfaz de Java que tiene un solo método abstracto. Se utilizan principalmente para trabajar con expresiones lambda y programación funcional.
Vienen anotadas con @FunctionalInterface. No es obligatoria, pero se recomienda.
@FunctionalInterface
interface Saludo {
void decirHola();
}
La Api de Java incluye en el paquete java.util.function una serie de interfaces funcionales que se utilizan en otras clases/interfaces de la Api o que podemos usar en nuestro propias clases.
Las implementaciones de estas interfaces son del tipo, consume un valor y retorna otro tipo de valor, o produce un valor sin argumentos o produce un valor dados dos argumentos.
A éstas se les llama unidades funcionales porque componen una lógica interna que a priori el consumidor de esta lógica no conoce, pero de la que sí se conoce su interfaz y por tanto la manera de relacionarse con el resto de los objetos, o lo que es lo mismo la manera de ser invocada
Las interfaces funcionales más importantes contenidas en java.util.function son:

- Predicate: Se utiliza en expresiones lambda para comprobar si una condición dada es verdadera o falsa
- Supplier: esta función se debe utilizar cuando se necesiten generar objetos sin requerir argumentos. Por ejemplo para realizar una inicialización perezosa.
- Consumer esta en cambio es el opuesto de Supplier ya que consume, acepta como argumento el tipo T sin devolver ningún valor de retorno.
Function<T,R>esta interfaz permite definir una función que acepta un parámetro de tipo T y devuelve un resultado del tipo R pudiendo aplicarle alguna transformación u operación.BiFunction<T,R,S>esta interfaz permite definir una función que acepta dos parámetros de tipo T y R, devolviendo un resultado del tipo S. Normalmente serán operaciones de agregación u operadores binarios como la suma, resta, etc..
Predicate¶
Predicate la interfaz predicado debe devolver forzosamente un boolean dado un objeto de tipo T, normalmente utilizado para filtrar elementos de una colección.
Recibe un valor y devuelve true o false
Esta es la estructura de la interfaz Predicate:
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
Como vemos la interfaz Predicate utiliza los genéricos para poder decirle que tipo concreto vamos a utilizar.
Ejemplo:
public class EjemploPredicate {
public static void main(String[] args) {
Predicate<Integer> esMayorQue10 = n -> n > 10;
System.out.println(esMayorQue10.test(15)); // true
Predicate<String> checker = a -> a.startsWith("M");
System.out.println(checker.test("Miguel"));
}
}
Nos permitirá recibir en un método como parámetro una condición independiente del algoritmo.
El siguiente ejemplo el método recibe una lista y un objeto de tipo predicate que será la condición. Imprime aquellos elementos que cumplen la condición
public static void procesar(List<Integer> lista, Predicate<Integer> condicion) {
for (Integer n : lista) {
//será True/False en función del predicate
if (condicion.test(n)) {
System.out.println(n);
}
}
}
public static void main(String[] args) {
List<Integer> numeros = List.of(10, 22, 8, 15, 4);
// Predicate: números pares
Predicate<Integer> esPar = n -> n % 2 == 0;
// Llamada al método pasando el predicate
procesar(numeros, esPar);
// Predicate: menores que 10
Predicate<Integer> esMenor10 = n -> n < 10;
// Llamada al método pasando el predicate
procesar(numeros, esMenor10);
}
Métodos Predicate¶
La interfaz Predicate contiene algunos métodos como:
isEqual(Object targetRef): Devuelve un predicado que prueba si dos argumentos son iguales.and(Predicate other): Devuelve un predicado compuesto que representa un AND lógico de este predicado y otro.or(Predicate other): Devuelve un predicado compuesto que representa un OR lógico de este predicado y otro.negate(): Devuelve un predicado que representa la negación lógica de este predicado.
Ejemplo:
Predicate<Integer> greaterThan10 = i -> i > 10;
Predicate<Integer> lessThan20 = i -> i < 20;
//si es mayor de 10 y menor de 20->true
Predicate<Integer> andPredicate=greaterThan10.and(lessThan20);
System.out.println(andPredicate.test(15));//true
//negate, niega el resultado del predicado
System.out.println(andPredicate.negate().test(15));//false
//isEquals, Devuelve un predicado que prueba si dos argumentos son iguales
//según Objects.equals(Object, Object).
Predicate<String> compara = Predicate.isEqual("hola");
System.out.println(compara.test("java"));//false
System.out.println(compara.test("hola"));//true
// menores de 10 y pares
procesar(numeros, esMenor10.and(esPar));
//mayores o iguales de 10
procesar(numeros, esMenor10.negate())
//impares menores de 10
procesar(numeros,esPar.negate().and(esMenor10))
Nota
Debido al uso extendido de Predicate se han añadido las interfaces funcionales IntPredicate cuando queremos trabajar con predicados de tipo entero, DoublePredicate y LongPredicate. También tenemos la interfaz BiPredicate que es un caso especial de Predicate y recibe dos parámetros en vez de uno.
IntPredicate predicate = (x) -> {
if (x == 12345) {
return true;
}
return false;
};
System.out.println(predicate.test(12345));
//creamos otro predicado que niega el anterior
IntPredicate intPredicate1 = predicate.negate();
System.out.println(intPredicate1.test(12345));
BiPredicate<String, Integer> filtroLongitud = (x, y) -> {
return x.length() == y;
};
boolean result = filtroLongitud.test("java", 10);
System.out.println(result); // false
Supplier¶
Supplier es otra interfaz funcional dentro del paquete java.util.function que nos provee del método abstracto **get**, sin argumentos que devuelve un tipo de dato.
@FunctionalInterface
public interface Supplier<T> {
T get();
}
Esta interfaz también se utiliza con expresiones lambda que no tienen parámetros pero devuelven un resultado:
Random random = new Random();
//devuelve un número aleatorio entre 1..6
Supplier<Integer> dados = () -> random.nextInt(6)+1;
System.out.println("Jugada 1: " +dados.get());
System.out.println("Jugada 2: " +dados.get());
Al igual que ocurría en los predicados con los Supplier también disponemos de las clases IntSupplier, DoubleSupplier, LongSupplier y BooleanSupplier.
Consumer¶
Consumer es otra interfaz funcional dentro del paquete java.util.function que provee un método que recibe un solo parámetro de tipo genérico y no devuelve nada.
public interface Consumer<T> {
void accept(T t);
}
La expresión lambda asignada a un objeto de tipo Consumer se usa para definir su método **accept(T t)** que eventualmente aplica la operación dada en su argumento. Los Consumer son útiles cuando no necesitan devolver ningún valor, ya que se espera que operen a través de efectos secundarios.
Existen también las interfaces IntConsumer, LongConsumer y DoubleConsumer.
Consumer<Integer> doble = (x) -> System.out.println(x*2);
doble.accept(5);
default void forEach(Consumer<? super T> action)
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("java");
list.add("lambda");
list.add("test");
list.forEach(
//n es el elemento actual de la lista
(n)->System.out.println(n)
);
List<Integer> list2=new ArrayList<Integer>();
list2.add(1);
list2.add((2));
list2.add(3);
list2.forEach(
n->System.out.println(
n%2==0?"Es par":"Es impar"
)
);
}
BiConsumer es un caso especial de las expresiones Consumer, son aquellas que reciben dos valores como parámetro y no devuelven resultado.
@FunctionalInterface
public interface BiConsumer<T, U> {
void accept(T t, U u);
}
Ejemplo:
BiConsumer<Integer, String> biConsumer = (x, s) -> System.out.println(x + s);
biConsumer.accept(3, " puntos");
Biconsumer y Map¶
Map tiene un método abstracto que foreach que recibe un Biconsumer, donde el primer valor es la clave y el segundo el valor
void forEach(BiConsumer<? super K,? super V> action)
Map<String, Integer> edades = new HashMap<>();
edades.put("Ana", 25);
edades.put("Luis", 30);
edades.put("Marta", 28);
BiConsumer<String, Integer> imprimir =
(nombre, edad) -> System.out.println(nombre + ": " + edad);
edades.forEach(imprimir);
edades.forEach(
(nombre, edad) ->
System.out.println(nombre + ": " + edad)
);
Function ¶
Nos permite recibir un parámetro, ejecutar un código y devolver un valor mediante el método apply.
Su principal uso es la transformación de un valor en otro
Function<String, Integer> funcionLambda = (s) -> {
int total = 0;
for (int i = 0; i < s.length(); i++) {
//suma el valor del código Unicode del caracter
total+=s.charAt(i);
}
return total;
};
System.out.println(funcionLambda.apply("Programación"));
//devuelve el cuadrado
Function<Integer, Integer> cuadrado =
n -> n * n;
int resultado = cuadrado.apply(4);
System.out.println("Cuadrado: " + resultado);
// Método que transforma una lista usando Function
public static void transformarLista(List<Integer> lista, Function<Integer, Integer> funcion) {
for (int i = 0; i < lista.size(); i++) {
int valor = lista.get(i);
int nuevoValor = funcion.apply(valor);
lista.set(i, nuevoValor);
}
}
public static void main(String[] args) {
List<Integer> numeros = List.of(1, 2, 3, 4);
// 1ª llamada: multiplicar por 2
List<Integer> copia1 = new ArrayList<>(numeros);
transformarLista(copia1, n -> n * 2);
System.out.println(copia1);
// 2ª llamada: sumar 10
List<Integer> copia2 = new ArrayList<>(numeros);
transformarLista(copia2, n -> n + 10);
System.out.println(copia2);
// 3ª llamada: cuadrado
List<Integer> copia3 = new ArrayList<>(numeros);
transformarLista(copia3, n -> n * n);
System.out.println(copia3);
}
[2, 4, 6, 8]
[11, 12, 13, 14]
[1, 4, 9, 16]
Al igual que las otras interfaces, tenemos variantes que nos simplifican el código
| Interfaz | Firma | Tipo de transformación | Ejemplo de lambda |
|---|---|---|---|
Function<T,R> |
T → R |
Objeto → objeto | s -> s.length() |
BiFunction<T,U,R> |
(T,U) → R |
Dos objetos → objeto | (a,b) -> a + b |
UnaryOperator<T> |
T → T |
Objeto → mismo objeto | n -> n * n |
BinaryOperator<T> |
(T,T) → T |
Dos objetos → mismo objeto | (a,b) -> a + b |
Especializadas para transformar Objeto a primitivo
| Interfaz | Firma | Transformación | Ejemplo |
|---|---|---|---|
ToIntFunction<T> |
T → int |
Objeto → int | s -> s.length() |
ToDoubleFunction<T> |
T → double |
Objeto → double | p -> p.getPrecio() |
ToLongFunction<T> |
T → long |
Objeto → long | d -> d.getTime() |
Especializadas para transformar primitivo a primitivo
| Interfaz | Firma | Transformación | Ejemplo |
|---|---|---|---|
IntToDoubleFunction |
int → double |
int → double | n -> n * 1.5 |
IntToLongFunction |
int → long |
int → long | n -> n * 1000L |
DoubleToIntFunction |
double → int |
double → int | d -> (int) d |
DoubleToLongFunction |
double → long |
double → long | d -> (long) d |
LongToIntFunction |
long → int |
long → int | l -> (int) l |
LongToDoubleFunction |
long → double |
long → double | l -> l / 2.0 |