Skip to content

3 Alguna interfaces funcionales

Consumer y BiConsumer

  • Consumer<T>: su método abstracto es void accept(T t).
  • BiConsumer<T, U>: su método abstracto es void 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 es boolean test(T t).
  • BiPredicate<T>: su método abstracto es boolean 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étodocomputeIfAbsentde 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 Functionpredefinidos 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 objeto Comparator para ordenar por el orden natural. Pero, ¿qué considera Java como orden natural? El orden indicado en la implementación de Comparable.

    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 elementos null precederán a los que no lo sean, que serán ordenados atendiendo al comparador recibido. También tenemos nullsLast, similar al anterior pero los elementos que sean nullse 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 o comparingDouble

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.