Java 8 tips: métodos default en la interfaz funcional Function

Como vimos en mi anterior post de Java 8, la interfaz Predicate nos ofrece varios métodos ‘default’ que pueden ser muy útiles. El resto de FuntionalInterface de Java 8 también tiene otros. En el caso de la interfaz Function, tiene alguno interesante, vamos a verlo en este post.

Concepto

Continuando con el post sobre métodos ‘default’ en la interfaz Predicate, vamos a ver aquí que otros métodos aporta la interfaz Function.

La interfaz Function representa una función, que acepta un parámetro y devuelve otro. Es una interfaz muy genérica, que se puede usar para muchos casos, e incluso es extendida por otras interfaces funcionales, como UnaryOperator (es igual que Function, pero los párametros de entrada y de salida son del mismo tipo).

Además, tiene varías implementaciones similares, como son BiFunction (como Function, pero con 2 parámetros de entrada).

Su implementación básica es la siguiente:

@FunctionalInterface
public interface Function<T, R> {

    R apply(T t);
}

Su método de interfaz funcional, ‘apply‘, recibe el parámetro y devuelve el resultado.

Pero, al igual que Predicate, tiene unos métodos ‘default’ extra, así como un método estático:

default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
   ...
}

default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
    ...
}

static <T> Function<T, T> identity() {
    ...
}

Ejemplo:

Al igual que en el anterior post, todos los ejemplos los haremos sobre un List de BookCharacter:

public class BookCharacter {
    private final String name;
    private final Integer age;
    private final Weapon mainWeapon;
    private final boolean human;

    public BookCharacter(String name, Integer age, Weapon mainWeapon, boolean human) {
        this.name = name;
        this.age = age;
        this.mainWeapon = mainWeapon;
        this.human = human;
    }

    public enum Weapon {SWORD, AXE, BOW, STAFF, RING}

    // getters, toString, equals and hashCode
}
...
List<BookCharacter> bookCharacters = Arrays.asList(
        new BookCharacter("Gandalf", Integer.MAX_VALUE, Weapon.STAFF, false),
        new BookCharacter("Aragorn", 88, Weapon.SWORD, true),
        new BookCharacter("Gimli", 140, Weapon.AXE, false),
        new BookCharacter("Legolas", 2931, Weapon.BOW, false),
        new BookCharacter("Boromir", 41, Weapon.SWORD, true),
        new BookCharacter("Frodo", 51, Weapon.RING, false),
        new BookCharacter("Sam", 33, Weapon.SWORD, false)
);

Tanto el método ‘compose‘ como el ‘andThen‘ hacen exactamente lo mismo, que es realizar una composición de funciones. Lo único que los diferencia es el orden en el que lo hacen. Dependiendo de cual nos guste o encaje más (principalmente en legibilidad) elegiremos una u otra.

Imaginemos que tenemos definidas estas dos funciones:

    public static Function<List<BookCharacter>, BookCharacter> findFirstSwordsman() {
        return list ->
                list.stream()
                    .filter(bookChar -> Weapon.SWORD.equals(bookChar.getMainWeapon()))
                    .findFirst()
                    .orElse(new BookCharacter("NOBODY", 0, Weapon.SWORD, true));
    }

    public static Function<BookCharacter, String> characterToCode() {
        return bookCharacter ->
                bookCharacter.getName() + "::" + bookCharacter.getMainWeapon() + "::" +
                        (bookCharacter.isHuman() ? "human" : "nonHuman");
    }

La primera nos devuelve el primer BookCharacter espadachín de un List de BookCharacter (devolviendo un ‘NODOBY’ si no encontrará ninguno).
La segunda nos devuelve un String formado a partir de un BookCharacter.

Vamos a crear otra función que sea una composición de esas dos funciones, usando los métodos ‘andThen’ y ‘compose’:

        Function<List<BookCharacter>, String> andThenFunction =
                findFirstSwordsman().andThen(characterToCode());

        Function<List<BookCharacter>, String> composeFunction =
                characterToCode().compose(findFirstSwordsman());

Esas dos funciones, tal cual las hemos definido hacen lo mismo. La diferencia, como comenté antes, es el orden.
En el primer caso, con el método ‘andThen‘ primero se ejecuta la función sobre la que llamamos a andThen, y sobre el resultado de esta, se llama a la función pasada como parámetro.
En el segundo caso, con el método ‘compose‘, primero es ejecutada la función pasada como parámetro, y sobre el resultado de esta, se llama a la función sobre la que llamamos a compose.

Por último, Function nos provee de un método estático llamado ‘identity‘. Este método devuelve la llamada función ‘identidad’, que no es más que un Function que devuelve exactamente lo mismo que ha recibido como parámetro.
Por ejemplo, si a partir de nuestro List de BookCharacter queremos obtener un Map, con los nombres como claves y los BookCharacter como valores, haríamos lo siguiente:

        Map<String, BookCharacter> characterMap =
                bookCharacters.stream()
                              .collect(Collectors.toMap(BookCharacter::getName,
                                      bookChar -> bookChar));

Como vemos, usamos el método ‘toMap‘ de Collectors que recibe dos funciones, una para obtener la clave, y otra para el valor. En nuestro caso, el valor es exactamente el objeto recibido, luego tenemos ese ‘bookChar -> bookChar’.

Podemos en lugar de eso usar la función identidad de Function:

        Map<String, BookCharacter> characterMap =
                bookCharacters.stream()
                              .collect(Collectors.toMap(BookCharacter::getName,
                                      Function.identity()));

¿Sirve para algo usar la función identidad en lugar de lo anterior? No especialmente. Quizá por legibilidad nos guste más usar la función identidad, o quizá no… al gusto de cada uno 🙂

Tenéis en mi github todo el código usado en este post, en el proyecto java8-function-methods-example.

Java 8 tips: métodos default en la interfaz funcional Predicate

Hace poco descubrí que las interfaces funcionales añadidas en Java 8 (Predicate, Function, etc) no solo tienen su correspondiente método abstracto, sino que además incluye unos métodos ‘default‘ muy útiles. Vamos a ver aquí en concreto los de la interfaz Predicate.

Concepto

Uno de las más importantes novedades en Java 8 son las lambdas. Para poder usarlas fácilmente, se han añadido varias interfaces funcionales (FunctionalInterface) para no tener que implementarlas nosotros, como son Function, Consumer, Supplier, Predicate

Estas interfaces, al ser FunctionalInterface solo tienen un único método. La interfaz Predicate, por ejemplo:

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

La interfaz Predicate sirve para evaluar una condición, devolviendo un booleano, y recibiendo un objeto como parámetro.
Su único método es el método ‘test‘, que realiza esa evaluación.

Pero si vemos el código de la interfaz Predicate veremos que también tiene varios métodos ‘default‘, que son los siguientes:

default Predicate<T> and(Predicate<? super T> other) {
    ...
}

default Predicate<T> negate() {
    ...
}

default Predicate<T> or(Predicate<? super T> other) {
    ...
}

Aunque quizá en principio no los necesitemos, en algunos casos pueden ser útiles, sobre todo a la hora de tener un código más legible. Vamos a ver un ejemplo de cómo podríamos usar esos métodos.

Ejemplo

Todos los ejemplos los haremos sobre un List de BookCharacter:

public class BookCharacter {
    private final String name;
    private final Integer age;
    private final Weapon mainWeapon;
    private final boolean human;

    public BookCharacter(String name, Integer age, Weapon mainWeapon, boolean human) {
        this.name = name;
        this.age = age;
        this.mainWeapon = mainWeapon;
        this.human = human;
    }

    public enum Weapon {SWORD, AXE, BOW, STAFF, RING}

    // getters, toString, equals and hashCode
}
...
List<BookCharacter> bookCharacters = Arrays.asList(
        new BookCharacter("Gandalf", Integer.MAX_VALUE, Weapon.STAFF, false),
        new BookCharacter("Aragorn", 88, Weapon.SWORD, true),
        new BookCharacter("Gimli", 140, Weapon.AXE, false),
        new BookCharacter("Legolas", 2931, Weapon.BOW, false),
        new BookCharacter("Boromir", 41, Weapon.SWORD, true),
        new BookCharacter("Frodo", 51, Weapon.RING, false),
        new BookCharacter("Sam", 33, Weapon.SWORD, false)
);

Si quisiéramos filtrar de nuestra lista los personajes que son jóvenes y que además lleven espada, haríamos algo así:

List<BookCharacter> youngsAndSwords =
        bookCharacters.stream()
                      .filter(bChar -> bChar.getAge() < 90 && Weapon.SWORD.equals(bChar.getMainWeapon()))
                      .collect(Collectors.toList());

o así:

List<BookCharacter> youngsAndSwords =
        bookCharacters.stream()
                      .filter(bChar -> bChar.getAge() < 90)
                      .filter(bChar -> Weapon.SWORD.equals(bChar.getMainWeapon()))
                      .collect(Collectors.toList());

La recomendada y más ‘clean code’ es la segunda manera, para separar los filtros.

Esta es la manera común de implementar las lambdas, pero en algunos casos nos puede interesar tener nuestras lambdas definidas en una variable, atributo o incluso una clase, para reutilizarla en más sitios. Algo como esto:

package com.edwise.pocs.java8predicatemethods;

// imports

public class BookCharacterPredicate {
    public static Predicate<BookCharacter> isYoung() {
        return bChar -> bChar.getAge() < 90;
    }

    public static Predicate<BookCharacter> useSword() {
        return bChar -> BookCharacter.Weapon.SWORD.equals(bChar.getMainWeapon());
    }

    public static Predicate<BookCharacter> isHuman() {
        return BookCharacter::isHuman;
    }

    public static Predicate<BookCharacter> isValid() {
        return bChar -> bChar.getName() != null &&
                bChar.getAge() > 0 &&
                bChar.getMainWeapon() != null;
    }
}

De esa manera, podríamos reescribir nuestro stream así:

List<BookCharacter> youngsAndSwords =
        bookCharacters.stream()
                      .filter(isYoung())
                      .filter(useSword())
                      .collect(Collectors.toList());

o así:

List<BookCharacter> youngsAndSwords =
        bookCharacters.stream()
                      .filter(isYoung().and(useSword()))
                      .collect(Collectors.toList());

En el segundo caso usamos el método ‘and‘ de Predicate, que acepta otro Predicate, y, por supuesto, hace un and lógico sobre los dos.

Si queremos filtrar los que no usan espada:

List<BookCharacter> notUseSword =
        bookCharacters.stream()
                      .filter(useSword().negate())
                      .collect(Collectors.toList());

Usamos el método ‘negate‘ que lo que hace es devolver la negación lógica de nuestro Predicate.

Y si queremos un filtro algo retorcido que nos devuelva los que no son humanos o usan espada:

List<BookCharacter> notHumanOrSwords =
        bookCharacters.stream()
                      .filter(isHuman().negate().or(useSword()))
                      .collect(Collectors.toList());

Aquí, además de ‘negate’, usamos el método ‘or‘, que hace un OR lógico con los dos Predicates.

Otra manera de usar nuestros Predicate podría ser tener un método que recibe, entre otras cosas, ese Predicate, por ejemplo:

public class BookCharacterChecker {

    public void doSomeStuffIfThisAndValid(BookCharacter bChar,
                                          Predicate<BookCharacter> predicate) {
        if (predicate.and(BookCharacterPredicate.isValid()).test(bChar)) {
            // do some stuff
            System.out.println("doing stuff with result true");
        } else {
            // do other stuff
            System.out.println("doing stuff with result false");
        }
    }
}

...

BookCharacter gandalf =
        new BookCharacter("Gandalf", Integer.MAX_VALUE, Weapon.STAFF, false);
BookCharacterChecker bookCharacterChecker = new BookCharacterChecker();

bookCharacterChecker.doSomeStuffIfThisAndValid(gandalf, bChar -> bChar.getAge() > 90);

En este caso, recibimos la lambda como parámetro, un Predicate, realizamos un and con otro Predicate y hacemos una cosa u otra según se cumpla.

Bonus: método estático isEqual

La interfaz Predicate tiene también un método static ‘isEqual‘, que devuelve un Predicate. Nos sirve para crear Predicates para comprobar igualdad. Por ejemplo:

BookCharacter aragorn = new BookCharacter("Aragorn", 88, Weapon.SWORD, true);
Predicate<BookCharacter> equalToAragorn = Predicate.isEqual(aragorn);

List<BookCharacter> allExceptAragorn =
        bookCharacters.stream()
                      .filter(equalToAragorn.negate())
                      .collect(Collectors.toList());

Aquí creamos con el método ‘isEqual‘ un Predicate que evalúe si un objeto es igual al personaje Aragorn. Luego filtramos nuestro stream negándolo, luego el List resultante debería devolver todos excepto Aragorn.

Hasta aquí por hoy, todo el código del post está en mi github, proyecto java8-predicate-methods-example.

Java 8 tips: collectingAndThen en Streams

Como apasionado de java que soy, la última versión me ha encantado por todos los cambios y mejoras que incluye. Por ello, comienzo hoy una nueva serie de posts relacionados con Java 8,  serán pequeños posts con curiosidades que me hayan llamado la atención o ‘tips’ que me parezcan especialmente útiles de la actual versión de la JDK.

Empiezo este, con un método interesante de la clase Collectors, usada al «terminar» un stream, para agrupar los elementos.

Concepto

Los Streams en java constan principalmente de 2 tipos de métodos u operaciones que pueden realizarse sobre ellos:

  • intermediate operations
  • terminal operations

Las ‘terminal’ son operaciones que pueden verse como operaciones para obtener un resultado final del stream (ya sea para obtener una lista, un sumatorio, etc).

Una de las más comunes es ‘collect‘:

   List<String> characters = Stream.of("Geralt", "Triss", "Yennefer", "Cirilla")
                                        .collect(Collectors.toList());

Lo que hace es agrupar los datos del stream y devolverlos en una Collection. En este caso, en un List.

Collectors.collectingAndThen

Imaginemos ahora que en el ejemplo anterior queremos que el list que nos devuelva el stream sea una lista inmutable. La idea es hacer esto:

        List<String> characters = Stream.of("Geralt", "Triss", "Yennefer", "Cirilla")
                                        .collect(Collectors.toList());
        List<String> charactersImmutable = Collections.unmodifiableList(characters);

Con Java 8 podemos hacerlo de una manera más elegante, usando el método ‘collectingAndThen‘ de Collectors:

    List<String> charactersImmutable =
                Stream.of("Geralt", "Triss", "Yennefer", "Cirilla")
                      .collect(Collectors.collectingAndThen(Collectors.toList(),
                                                            c -> Collections.unmodifiableList(c)));

Como vemos, ‘collectingAndThen’ nos proporciona la posibilidad de realizar una tarea ‘extra’ después de realizar el ‘collect’ en si mismo. En este caso, una vez obtenido el List, creamos con él una lista inmutable, que es lo que al final devolverá todo el procesamiento del stream.

Y por supuesto, podemos hacerlo más limpio aún, haciendo que sea una ‘method reference’:

    List<String> charactersImmutable =
                Stream.of("Geralt", "Triss", "Yennefer", "Cirilla")
                      .collect(Collectors.collectingAndThen(Collectors.toList(),
                                                            Collections::unmodifiableList));

El método ‘unmodifiableList‘ de Collections puede usarse perfectamente como ‘method reference’, quedando más limpio el código.

Gracias al método ‘collectingAndThen’ de Collectors podemos realizar alguna acción sobre la Collection (List, Map, etc) que queramos sacar del stream, antes de devolverla, quedando el código mucho más elegante 🙂

Espero que os sea útil. Tenéis el código de este post en mi github, proyecto java8-collectingandthen-example.