3 Alguna interfaces funcionales¶
Consumer y BiConsumer¶
Consumer<T>
: su método abstracto esvoid accept(T t)
.BiConsumer<T, U>
: su método abstracto esvoid accept(T t, U u)
.
La interfaz Consumer
es empleada por el método forEach(Consumer<T> action)
de la interfaz Iterable
, que ejecuta la acción indicada sobre cada elemento del iterable.
import java.util.List;
public class InterfaceConsumer {
public void show() {
List<Integer> list = List.of(3, 2, 6, 1, 5, 4);
list.forEach(System.out::println); // num -> System.out.println(num)
}
public static void main(String[] args) {
new InterfaceConsumer().show();
}
}
En este ejemplo, se ejecuta la acción de mostrar por consola una línea con cada elemento de la lista.
La interfaz funcional BiConsumer
es similar a Consumer
pero su método recibe dos argumentos, uno de tipo T
y otro de tipo U
y no retorna nada: void accept(T t, U u)
import java.util.HashMap;
import java.util.Map;
public class InterfaceBiConsumer {
public void show() {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < 5; i++) {
map.put(i, i * i);
}
map.forEach((k, v) -> System.out.printf("Clave:%d Valor:%d\n", k, v));
}
public static void main(String[] args) {
new InterfaceBiConsumer().show();
}
}
La interfaz funcional Consumer
posee un método default llamado andThen(Consumer<T> after)
que llama al método accept
del consumidor recibido después de haber llamado a su propio accept
. Gracias a este método, podemos tener una serie de objetos Consumer
predefinidos y encadenarlos de la forma que nos interese.Veamos un ejemplo:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
public class ConsumerAndThen{
public void show(){
List<Integer> list = List.of(3, 2, 6, 1, 5, 4);
List<Integer> listCopy = new ArrayList<>();
Consumer<Integer> copy = listCopy::add; // (value) -> listCopy.add(value);
Consumer<Integer> show = System.out::println; // (value) -> System.out.println(value);
list.forEach(copy.andThen(show));
listCopy.foreach(show);
}
public static void main(String[] args){
new ConsumerAndThen().show();
}
}
Predicate y BiPredicate¶
Predicate<T>
: su método abstracto esboolean test(T t)
.BiPredicate<T>
: su método abstracto esboolean test(T t, U u)
.
Las interfaces funcionales Predicate
y BiPredicate
poseen métodos default que retornan un nuevo objeto que implementa la misma interfaz y que permiten componer predicados mediante operaciones lógicas, como or(otherPredicate)
, and(otherPredicate)
o negate(otherPredicate)
. El orden en el que se ejecutarán serán el orden en el que aparecen en la composición, es decir, no existe una prioridad preestablecida como con los operadores lógicos.
Veamos un ejemplo utilizando el método removeIf
de la interfaz Collection
que recibe un Predicate por parámetro: default boolean removeIf(Predicate<? super E> filter)
. Dicho método elimina de la colección aquellos elementos que cumplan el Predicate:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
public class PredicateComposition{
public void show(){
List<Integer> list = new ArrayList<>(List.of(3, 2, 6, 1, 5, 4));
Predicate<Integer> esPar = n -> n % 2 == 0;
Predicate<Integer> mayorQue3 = n -> n > 3;
list.removeIf(esPar.and(mayorQue3));
list.forEach(System.out::println);
}
public static void main(String[] args){
new PredicateComposition().show();
}
}
La interfaz Predicate
tiene también un método estático factoría PredicateIsEqual(Object o)
que retorna el predicado correspondiente a comprobar si un elemento es igual que otro objeto. Internamente simplemente se llamará al método equals
:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
public class PredicateIsEqual{
public void show(){
List<Integer> list = new ArrayList<>(List.of(3, 2, 6, 1, 5, 4));
list.removeIf(Predicate.isEqual(5));
list.forEach(System.out::println);
}
public static void main(String[] args){
new PredicateIsEqual().show();
}
}
Function¶
Function<T,R>
: su método abstracto es R apply(T t)
. Veamos un ejemplo de utilización en el métodocomputeIfAbsent
de los mapas:
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
public class InterfaceFunction{
public void show(){
Function<Integer, Integer> elevarAlCuadrado = n -> n * n;
Map<Integer, Integer> map = new HashMap<>();
for(int i = 0; i < 10; i++){
map.computeIfAbsent(i, elevarAlCuadrado);
}
map.forEach((k,v) -> System.out.printf("Clave:%d Valor:%d\n", k, v));
}
public static void main(String[] args){
new InterfaceFunction().show();
}
}
La interfaz funcional Function
posee un método default llamado andThen(afterFunction)
que permite que después de la ejecución del método apply
de la function original, su resultado se pase como valor de entrada del método apply
del objeto Function
pasado como argumento, retornado el objeto Function
correspondiente a la cadena de operaciones de transformación. Veamos un ejemplo:
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
public class FunctionAndThen{
public void show(){
Function<Integer, Integer> elevarAlCuadrado = n -> n * n;
Function<Integer, String> aCadena = String::valueOf; // value -> String.valueOf(value)
Map<Integer, String> map = new HashMap<>();
for(int i = 0; i < 10; i++){
map.computeIfAbsent(i, elevarAlCuadrado.andThen(aCadena));
}
map.forEach((k,v) -> System.out.printf("Clave:%d Valor:%d\n", k, v));
}
public static void main(String[] args){
new FunctionAndThen().show();
}
}
Muy parecido al anterior es el método default compose(beforeFunction)
que primero ejecuta el método apply
del objeto Function
recibe y después el método apply
del objeto Function
original, es decir, se ejecutan en orden inverso al de andThen
. Gracias a estos dos métodos, podemos tener una serie de objetos Function
predefinidos y encadenarlos de la forma que nos interese.
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
public class FunctionCompose{
public void show(){
Function<Integer, Integer> elevarAlCuadrado = n -> n * n;
Function<Integer, String> aCadena = String::valueOf; // value -> String.valueOf(value)
Map<Integer, String> map = new HashMap<>();
for(int i = 0; i < 10; i++){
map.computeIfAbsent(i, aCadena.compose(elevarAlCuadrado));
}
map.forEach((k,v) -> System.out.printf("Clave:%d Valor:%d\n", k, v));
}
public static void main(String[] args){
new FunctionCompose().show();
}
}
Comparator¶
La interfaz funcional Comparator
(comparador) contiene el método abstracto int compare(T o1, T o2)
que recibe dos valores de tipo T
y retorna un entero que vale:
- 0 si o1 es igual a o2.
- Menor que 0 si o1 es menor que o2.
- Mayor que 0 si o1 es mayor que o2.
La interfaz Comparator
ya existía en Java 7 y de hecho no se encuentra en el paquete java.util.function
, sino directamente en java.util
. Esta interfaz posee una serie de métodos estáticos factoría que retornan objetos Comparator
correspondientes a los casos más habituales de comparación:
-
static <T extends Comparable<? super T>> Comparator<T> naturalOrder()
: devuelve un objetoComparator
para ordenar por el orden natural. Pero, ¿qué considera Java como orden natural? El orden indicado en la implementación deComparable
.import java.util.Arrays; import java.util.Comparator; import java.util.List; public class NaturalOrder{ public void show(){ List<Integer> list = Arrays.asList(3, 2, 6, 1, 5, 4); list.sort(Comparator.naturalOrder()); list.forEach(System.out::println); } public static void main(String[] args){ new NaturalOrder().show(); } }
-
static <T extends Comparable<? super T>> Comparator<T> reverseOrder()
: ordena por el orden natural inverso:import java.util.Arrays; import java.util.Comparator; import java.util.List; public class ReverseOrder{ public void show(){ List<Integer> list = Arrays.asList(3, 2, 6, 1, 5, 4); list.sort(Comparator.reverseOrder()); list.forEach(System.out::println); } public static void main(String[] args){ new ReverseOrder().show(); } }
-
static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator)
: recibe un comparador y retorna un nuevo comparador según el cual los elementosnull
precederán a los que no lo sean, que serán ordenados atendiendo al comparador recibido. También tenemosnullsLast
, similar al anterior pero los elementos que seannull
se sitúan al final.import java.util.Arrays; import java.util.Comparator; import java.util.List; public class NullsFirst{ public void show(){ List<Integer> list = Arrays.asList(3, 2, null, 6, 1, 5, 4, null); list.sort(Comparator.nullsFirst(Comparator.naturalOrder())); System.out.println("nullsFirst: "); list.forEach(System.out::println); list.sort(Comparator.nullsLast(Comparator.naturalOrder())); System.out.println("nullsLast: "); list.forEach(System.out::println); } public static void main(String[] args){ new ReverseOrder().show(); } }
-
static <T, U extends Comparable<? super U>> Comparator<T> comparing(Function<? super T, ? extends U> keyExtractor)
: recibe una función que debe retornar el valor por el que debe comparar el comparador retornado. Este método es muy útil si queremos ordenar una lista de objetos por un determinado campo.public class Vehicle { private String registration; private int wheelCount; private double speed; private String colour; public Vehicle(String registration, int wheelCount, String colour) { this.registration = registration; this.wheelCount = wheelCount; this.colour = colour; speed = 0; } public int getWheelCount() { return wheelCount; } public double getSpeed() { return speed; } public String getColour() { return colour; } public void setColour(String colour) { this.colour = colour; } public String getRegistration() { return registration; } public void accelerate(double amount) { speed += amount; } public void brake(double amount) { speed -= amount; } @Override public String toString() { return "Vehicle [registration=" + registration + ", wheelCount=" + wheelCount + ", speed=" + speed + ", colour=" + colour + "]"; } }
public class Comparing{ public void show(){ List<Vehicle> list = new ArrayList<>(); list.add(new Vehicle("1705UBG", 4, "blanco")); list.add(new Vehicle("9685KMX", 4, "azul")); list.add(new Vehicle("1235GTR", 2, "rojo")); list.add(new Vehicle("7314QWE", 4, "verde")); list.add(new Vehicle("3495JZA", 2, "blanco")); list.add(new Vehicle("5930POI", 2, "negro")); list.sort(Comparator.comparing(Vehicle::getColour)); list.forEach(System.out::println); } public static void main(String[] args){ new Comparing().show(); } }
Vehicle [registration=9685KMX, wheelCount=4, speed=0.0, colour=azul] Vehicle [registration=1705UBG, wheelCount=4, speed=0.0, colour=blanco] Vehicle [registration=3495JZA, wheelCount=2, speed=0.0, colour=blanco] Vehicle [registration=5930POI, wheelCount=2, speed=0.0, colour=negro] Vehicle [registration=1235GTR, wheelCount=2, speed=0.0, colour=rojo] Vehicle [registration=7314QWE, wheelCount=4, speed=0.0, colour=verde]
Hay versiones de este método estático factoría para cuando el valor por que se debe ordenar es de un tipo primitivo, como
comparingInt
,comparingLong
ocomparingDouble
La interfaz funcional Comparator
también incorpora una serie de métodos default que nos permiten encadenar comparadores:
-
default Comparator<T> reversed()
: sirve para obtener el orden inverso al del comparador original.public class Reversed{ public void show(){ List<Vehicle> list = new ArrayList<>(); list.add(new Vehicle("1705UBG", 4, "blanco")); list.add(new Vehicle("9685KMX", 4, "azul")); list.add(new Vehicle("1235GTR", 2, "rojo")); list.add(new Vehicle("7314QWE", 4, "verde")); list.add(new Vehicle("3495JZA", 2, "blanco")); list.add(new Vehicle("5930POI", 2, "negro")); list.sort(Comparator.comparing(Vehicle::getColour).reversed()); list.forEach(System.out::println); } public static void main(String[] args){ new Reversed().show(); } }
Vehicle [registration=7314QWE, wheelCount=4, speed=0.0, colour=verde] Vehicle [registration=1235GTR, wheelCount=2, speed=0.0, colour=rojo] Vehicle [registration=5930POI, wheelCount=2, speed=0.0, colour=negro] Vehicle [registration=1705UBG, wheelCount=4, speed=0.0, colour=blanco] Vehicle [registration=3495JZA, wheelCount=2, speed=0.0, colour=blanco] Vehicle [registration=9685KMX, wheelCount=4, speed=0.0, colour=azul]
default <U extends Comparable<? super U>> Comparator<T> thenComparing(Function<? super T, ? extends U> keyExtractor)
: recibe una función con la que indica el valor por el que comparar si con el comparador original los elementos son iguales:
public class ThenComparingFunction{ public void show(){ List<Vehicle> list = new ArrayList<>(); list.add(new Vehicle("1705UBG", 4, "blanco")); list.add(new Vehicle("9685KMX", 4, "azul")); list.add(new Vehicle("1235GTR", 2, "rojo")); list.add(new Vehicle("7314QWE", 4, "verde")); list.add(new Vehicle("3495JZA", 2, "blanco")); list.add(new Vehicle("5930POI", 2, "negro")); list.sort(Comparator.comparing(Vehicle::getColour).thenComparing(Vehicle::getWheelCount)); list.forEach(System.out::println); } public static void main(String[] args){ new Reversed().show(); } }
Vehicle [registration=9685KMX, wheelCount=4, speed=0.0, colour=azul] Vehicle [registration=3495JZA, wheelCount=2, speed=0.0, colour=blanco] Vehicle [registration=1705UBG, wheelCount=4, speed=0.0, colour=blanco] Vehicle [registration=5930POI, wheelCount=2, speed=0.0, colour=negro] Vehicle [registration=1235GTR, wheelCount=2, speed=0.0, colour=rojo] Vehicle [registration=7314QWE, wheelCount=4, speed=0.0, colour=verde]
Existen versiones específicas de este método para cunado la función retorna un tipo primitivo, como thenComparingInt
, thenComparingLong
y thenComparingDouble
.