Review: Java 8 in Action

Después de un pequeño parón en el blog, debido a vacaciones y temas varios, vuelvo esta vez con una pequeña reseña sobre un libro que terminé de leer hace poco, «Java 8 in Action», de la editorial Manning.

Aunque ya estaba bastante familiarizado con Java 8, por leer múltiples artículos y hacer pocs y demás, me compré hace varios meses este libro. Y hice muy bien  🙂 Es un libro de muy fácil lectura y no se hace largo ni pesado.

Trata principalmente todas las novedades de Java 8, centrándose en concreto en el «cambio» a programación funcional de Java, centrandose mucho en las lambdas y los streams.

Los autores son Raoul-Grabriel Urma, Mario Fusco y Alan Mycroft.

Java 8 in Action

Java 8 in Action

Partes

Consta de 4 grandes partes, con varios capítulos cada una:

  1. Fundamentals: introducción en la que habla de porque necesitaba Java esta actualización, con una pequeña introducción a las lambdas y streams, así como un capitulo tratando el concepto de «Behavior parameterization» (capitulo muy didáctico, y que viene muy bien para entender correctamente las lambdas). El último capitulo de esta parte introduce a fondo el concepto de lambda y su funcionamiento.
  2. Functional-style data processing: en esta parte se centra a saco en los streams, con varios capítulos. Desde una introducción básica, hasta el uso detallado de los collectors, etc. En concreto, en uno de los capitulos trata la paralelización, muy interesante, haciendo incluso una pequeña introducción del framework fork/join que se añadió en Java 7.
  3. Effective Java 8 programming: en esta parte empieza con un capitulo con consejos de refactoring de java a java 8, consejos de testing y debugging. El resto de capítulos trata otras novedades de Java 8:
    – Default methods
    – Optional
    – CompletableFuture (se me quedo un pelín corto, pero es una muy buena introducción)
    – New Date Time API
  4. Beyond Java 8: En esta parte se centran en explicar varios conceptos de programación funcional pura, de como usarlos con Java 8, etc. Termina con un capitulo comparando Java 8 con Scala (con una pequeña introducción a Scala, no está mal), y con otro comentando posibles mejoras (ya para Java 9) que consideran recomendables.

Por último tiene 4 apéndices:
Apéndice A Comenta algunos otros pequeños cambios de Java 8.
Apéndice B Enumera nuevos métodos en librerías ya existentes (collection, list, etc)
Apéndice C Apendice avanzado sobre como realizar múltiples operaciones en paralelo con Streams. Curioso cuanto menos.
Apéndice D Pequeño apéndice comentando como las lambdas son traducidas a bytecode.

En la web de Manning se puede descargar además todo el código de ejemplo que sale en todo el libro. Además, si te compras el libro y lo registras en la web, puedes descargartelo en pdf, epub, formato kindle, etc. Muy cómodo.

Opinión personal

Como comentaba cuando empecé a leerlo ya tenia un gran background sobre Java 8, e incluso lo estaba usando en mi trabajo desde Febrero (que es cuando me cambie de empresa), con lo que casi me lo compré más por capricho que por necesidad. Y aún así no me arrepiento 🙂

El libro me vino muy bien para asentar ciertos conceptos, y para entender ciertas movidas de los streams. Además que viene muy bien como libro de consulta para ciertas operaciones.

Y si no estás muy puesto en Java 8, es un libro perfecto para ponerte las pilas y muy fácil de leer. No se hace nada pesado, y trata todo lo necesario para manejarte con el mundillo de las lambdas y streams.

En mi experiencia, si eres programador Java, el paso a Java 8 y su programación funcional es una pasada. El código es mucho más legible, más resumido… Y además es que luego ya no hay vuelta atrás, jaja. Si ahora mismo tuviera que volver a trabajar en algún proyecto en Java 7 (o menos), no podría…

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.

Futuros en Java, Parte 4: CompletableFuture, uso avanzado

Este último post de la serie de Futuros en Java es la continuación del anterior donde lo dejamos, viendo las funcionalidades de la clase CompletableFuture. Si no lo has leído ya, mejor empieza por ahí 🙂

Esta serie consta de las siguientes partes:
Parte 1: Introducción
Parte 2: interfaz Future
Parte 3: CompletableFuture, introducción
Parte 4: CompletableFuture, uso avanzado

Continúo con el uso de los CompletableFuture, a un nivel más avanzado:

Listeners / Callbacks
Como hemos visto, sobre esos futuros creados en los ejemplos anteriores podemos ahora ejecutar el método ‘get‘ de siempre para obtener el valor. Pero eso es justo lo que queríamos evitar. Para eso tenemos los callbacks y listeners, que serán ejecutados en cuanto el futuro se complete.
Además, la mayoría de estos métodos para crear callbacks devuelven a su vez un CompletableFuture también. Muy útil para encadenar varios futuros, como veremos después.
Todas tienen sus tres versiones, como ya expliqué en el anterior post.

whenComplete: este ya lo hemos visto en el anterior post, añade un callback para ejecutarlo cuando el futuro se complete. La lambda tiene 2 parámetros, uno es el posible resultado, y el otro es la excepción, si la hubiera habido. Veremos el tratamiento de excepciones más adelante.

thenApply: para transformar futuros. La idea es pasarle una función lambda que transforme el resultado del primero. Es similar al ‘map‘ de Scala.

    CompletableFuture<String> futureAsync = CompletableFuture.supplyAsync(() -> {
        LOGGER.info("Comenzando supplyAsync for thenApply...");
        Sleep.sleepSeconds(2);
        LOGGER.info("Terminado supplyAsync for thenApply!");
        return "Terminado";
    }, executor);

    CompletableFuture<String> futureApply = futureAsync.thenApplyAsync(s -> {
        LOGGER.info("Comenzando applyAsync...");
        Sleep.sleepSeconds(2);
        LOGGER.info("Terminado applyAsync!");
        return s.toUpperCase();
    }, executor);

    futureApply.whenCompleteAsync((s, e) -> LOGGER.info("Resultado applyAsync: {}", s),
        executor);

thenAccept y thenRun: muy similares al whenComplete, ejecutaran el lambda una vez se complete el futuro. El primero recibe un resultado, y el segundo no. Son equivalentes al supplyAsync y runAsync respectivamente.

    // thenAccept
    CompletableFuture<String> futureAsync = CompletableFuture.supplyAsync(() -> {
        LOGGER.info("Comenzando supplyAsync for thenAccept...");
        Sleep.sleepSeconds(2);
        LOGGER.info("Terminado supplyAsync for thenAccept!");
        return "Terminado";
    }, executor);

    futureAsync.thenAcceptAsync(s -> {
        LOGGER.info("Comenzando thenAccept...");
        Sleep.sleepSeconds(2);
        LOGGER.info("Terminado thenAccept!");
        LOGGER.info("Resultado: {}", s);
    }, executor);

    // thenRun
    CompletableFuture<Void> futureRun = CompletableFuture.runAsync(() -> {
        LOGGER.info("Comenzando runAsync for thenRun...");
        Sleep.sleepSeconds(2);
        LOGGER.info("Terminado runAsync for thenRun!");
    }, executor);

    futureRun.thenRunAsync(() -> {
        LOGGER.info("Comenzando thenRun...");
        Sleep.sleepSeconds(2);
        LOGGER.info("Terminado thenRun!");
    }, executor);

Excepciones
Tenemos varias maneras de gestionar las excepciones de futuros con la clase CompletableFuture, usando estos métodos:

exceptionally: registra un callback para gestionar la excepción. Recibe una lambda que solo tiene de parámetro la excepción, debe retornar un valor del mismo tipo que el futuro en el que se originó la excepción.

    CompletableFuture<String> futureAsync = CompletableFuture.supplyAsync(() -> {
        LOGGER.info("Comenzando supplyAsync with exception...");
        Sleep.sleepSeconds(2);
        LOGGER.info("Terminado supplyAsync with exception!");
        throw new RuntimeException("Error en el futuro");
    }, executor);

    CompletableFuture<String> futureEx = futureAsync.exceptionally(e -> {
        LOGGER.error("Resultado con excepción!!", e);
        return "StringPorDefecto";
    });

    futureEx.whenCompleteAsync((s, e) -> LOGGER.info("Resultado futureEx: {}", s),
            executor);

handle: registra un callback para gestionar el resultado o excepción. Recibe una lambda que tiene dos parámetros, el resultado y la excepción. Si la excepción no es nula, es que ha habido una excepción. También deber retornar un valor del tipo del futuro que lanzo la excepción.

    CompletableFuture<String> futureAsync = CompletableFuture.supplyAsync(() -> {
        LOGGER.info("Comenzando supplyAsync with exception...");
        Sleep.sleepSeconds(2);
        LOGGER.info("Terminado supplyAsync with exception!");
        throw new RuntimeException("Error en el futuro");
    }, executor);

    CompletableFuture<String> handledFuture = futureAsync.handleAsync((s, e) -> {
        if (e != null) {
            LOGGER.error("Resultado con excepción!!", e);
            return "StringPorDefecto";
        } else {
            LOGGER.info("Resultado: {}", s);
            return s;
        }
    }, executor);

    handledFuture.whenCompleteAsync((s, e) -> LOGGER.info("Resultado handle: {}", s),
            executor);

whenComplete: con este método que ya hemos explicado podemos hacer algo parecido al ‘handle’, dado que la lambda que registra tiene también los dos parámetros.

    CompletableFuture<String> futureAsync = CompletableFuture.supplyAsync(() -> {
        LOGGER.info("Comenzando supplyAsync with exception...");
        Sleep.sleepSeconds(2);
        LOGGER.info("Terminado supplyAsync with exception!");
        throw new RuntimeException("Error en el futuro");
    }, executor);

    futureAsync.whenCompleteAsync((s, e) -> {
        if (e != null) {
            LOGGER.error("Resultado con excepción!!", e);
        } else {
            LOGGER.info("Resultado applyAsync: {}", s);
        }
    }, executor);

Combinar futuros
En casi todos los ejemplos anteriores prácticamente solo hemos usado callbacks sobre un mismo futuro. Pero, como hemos visto en algún ejemplo (el ‘thenApply’), podemos encadenar futuros y combinarlos. En esta funcionalidad es donde se ve el verdadero potencial del desarrollo usando futuros.

Tenemos los siguientes métodos:

thenCompose: Muy similar a ‘thenApply’, pero este es equivalente al ‘flatMap’ de Scala. Lo que hace es una cadena de futuro también. Por ejemplo, aquí llamamos a ‘thenCompose’ con una lambda que a su vez es otro futuro:

    CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
        LOGGER.info("Comenzando supplyAsync for thenCompose...");
        Sleep.sleepSeconds(2);
        LOGGER.info("Terminado supplyAsync for thenCompose!");
        return "Terminado";
    }, executor);

    CompletableFuture<String> fCompose =
            future1.thenComposeAsync(s -> CompletableFuture.supplyAsync(() -> {
                        LOGGER.info("Comenzando thenCompose...");
                        Sleep.sleepSeconds(2);
                        LOGGER.info("Terminado thenCompose!");
                        return s.concat(" + Terminado other");
                    }, executor),
                    executor);

    fCompose.whenCompleteAsync((s, e) -> LOGGER.info("Resultado thenCompose: {}", s),
            executor);

Parece algo retorcido, pero puede tener su lógica 😉

thenCombine: En este caso, en lugar de una cadena de futuros, espera a que terminen dos futuros, para luego hacer algo. En este caso la lambda tendrá dos parámetros, que son el resultado de cada uno de los dos futuros:

        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
            LOGGER.info("Comenzando future1 for thenCombine...");
            Sleep.sleepSeconds(2);
            LOGGER.info("Terminado future1 for thenCombine!");
            return "Terminado";
        }, executor);

        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
            LOGGER.info("Comenzando future2 for thenCombine...");
            Sleep.sleepSeconds(1);
            LOGGER.info("Terminado future2 for thenCombine!");
            return "Terminado other";
        }, executor);

        CompletableFuture<String> fCombine =
                future1.thenCombineAsync(future2, (s1, s2) -> {
                    LOGGER.info("En el thenCombine, recibidos results: {}, {}", s1, s2);
                    return s1 + s2;
                }, executor);

        fCombine.whenCompleteAsync((s, e) -> LOGGER.info("Resultado thenCombine: {}", s),
                executor);

thenAcceptBoth y runAfterBoth: Muy similares al ‘thenCombine’, excepto que no generan un nuevo futuro, simplemente ejecutan la lambda cuando los dos futuros terminen. Es como un ‘whenComplete‘ pero esperando dos futuros:

    // thenAcceptBoth
    CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
        LOGGER.info("Comenzando future1 for thenAcceptBoth...");
        Sleep.sleepSeconds(2);
        LOGGER.info("Terminado future1 for thenAcceptBoth!");
        return "Terminado";
    }, executor);

    CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
        LOGGER.info("Comenzando future2 for thenAcceptBoth...");
        Sleep.sleepSeconds(1);
        LOGGER.info("Terminado future2 for thenAcceptBoth!");
        return "Terminado other";
    }, executor);

    future1.thenAcceptBothAsync(future2, (s1, s2) ->
                    LOGGER.info("En el thenAcceptBoth, recibidos results: {}, {}", s1, s2)
            , executor);

    // runAfterBoth
    CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
        LOGGER.info("Comenzando future1 for runAfterBoth...");
        Sleep.sleepSeconds(2);
        LOGGER.info("Terminado future1 for runAfterBoth!");
    }, executor);

    CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
        LOGGER.info("Comenzando future2 for runAfterBoth...");
        Sleep.sleepSeconds(1);
        LOGGER.info("Terminado future2 for runAfterBoth!");
    }, executor);

    future1.runAfterBothAsync(future2, () -> LOGGER.info("En el runAfterBoth, futuros terminados.")
            , executor);

acceptEither y runAfterEither: En algunos casos en que tengamos dos futuros nos interesará hacer algo cuando uno de los dos termine, el primero que lo haga. Para eso están estos dos métodos:

    // acceptEither
    CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
        LOGGER.info("Comenzando future1 for acceptEither...");
        Sleep.sleepSeconds(3);
        LOGGER.info("Terminado future1 for acceptEither!");
        return "Segundo";
    }, executor);

    CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
        LOGGER.info("Comenzando future2 for acceptEither...");
        Sleep.sleepSeconds(1);
        LOGGER.info("Terminado future2 for acceptEither!");
        return "Primero";
    }, executor);

    future1.acceptEitherAsync(future2, (s) ->
                    LOGGER.info("En el acceptEither, recibido el primer resultado: {}", s)
            , executor);

    // runAfterEither
    CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
        LOGGER.info("Comenzando future1 for runAfterEither...");
        Sleep.sleepSeconds(3);
        LOGGER.info("Terminado future1 for runAfterEither!");
    }, executor);

    CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
        LOGGER.info("Comenzando future2 for runAfterEither...");
        Sleep.sleepSeconds(1);
        LOGGER.info("Terminado future2 for runAfterEither!");
    }, executor);

    future1.runAfterEitherAsync(future2, () -> LOGGER.info("En el runAfterEither, primero terminado.")
            , executor);

applyToEither: muy similar a ‘acceptEither’, pero este devuelve a su vez un futuro. Es como el ‘thenApply’ pero sobre el futuro que termine antes:

    CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
        LOGGER.info("Comenzando future1 for applyToEither...");
        Sleep.sleepSeconds(3);
        LOGGER.info("Terminado future1 for applyToEither!");
        return "Segundo";
    }, executor);

    CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
        LOGGER.info("Comenzando future2 for applyToEither...");
        Sleep.sleepSeconds(1);
        LOGGER.info("Terminado future2 for applyToEither!");
        return "Primero";
    }, executor);

    CompletableFuture<String> applyToEitherFuture = future1.applyToEitherAsync(future2, s -> {
        LOGGER.info("Comenzando applyToEither...");
        Sleep.sleepSeconds(1);
        LOGGER.info("Terminado applyToEither!");
        return s.toUpperCase();
    }, executor);

    applyToEitherFuture.whenCompleteAsync((s, e) -> LOGGER.info("Resultado applyToEither: {}", s),
            executor);

allOf y anyOf: Hasta ahora parece que solo podíamos combinar dos futuros. Con estos dos métodos podemos hacer un ‘thenAcceptBoth’ o ‘acceptEither’ sobre un número ilimitado de futuros:

    // allOf
    CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
        LOGGER.info("Comenzando future1 for allOf...");
        Sleep.sleepSeconds(2);
        LOGGER.info("Terminado future1 for allOf!");
        return "Terminado future1";
    }, executor);

    CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
        LOGGER.info("Comenzando future2 for allOf...");
        Sleep.sleepSeconds(1);
        LOGGER.info("Terminado future2 for allOf!");
        return "Terminado future2";
    }, executor);

    CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> {
        LOGGER.info("Comenzando future3 for allOf...");
        Sleep.sleepSeconds(3);
        LOGGER.info("Terminado future3 for allOf!");
        return "Terminado future3";
    }, executor);

    CompletableFuture<Void> all = CompletableFuture.allOf(future1, future2, future3);

    all.whenCompleteAsync((s, e) -> LOGGER.info("Resultado all: {}", s), executor);

    // anyOf
    CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
        LOGGER.info("Comenzando future1 for allOf...");
        Sleep.sleepSeconds(2);
        LOGGER.info("Terminado future1 for allOf!");
        return "Terminado future1";
    }, executor);

    CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
        LOGGER.info("Comenzando future2 for allOf...");
        Sleep.sleepSeconds(1);
        LOGGER.info("Terminado future2 for allOf!");
        return "Terminado future2";
    }, executor);

    CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> {
        LOGGER.info("Comenzando future3 for allOf...");
        Sleep.sleepSeconds(3);
        LOGGER.info("Terminado future3 for allOf!");
        return "Terminado future3";
    }, executor);

    CompletableFuture<Object> all = CompletableFuture.anyOf(future1, future2, future3);

    all.whenCompleteAsync((s, e) -> LOGGER.info("Resultado any: {}", s), executor);

La única pega es que el tipo que devuelven es Void y Object respectivamente.
Lógicamente, si esperas a que terminen todos los futuros (allOf), y devuelven resultado, no quieres un resultado, querrás todos. Quizá deberían haber hecho que devolviera un List en lugar de Void.

Hasta aquí este pequeño tutorial de cómo usar los futuros en Java con la clase CompletableFuture. Espero que os sea útil. Tenéis todos los ejemplos y alguno más en mi github, proyecto completablefuture-example.

Futuros en Java, Parte 3: CompletableFuture, introducción

En este tercer post sobre Futuros en Java veremos la nueva clase CompletableFuture, añadida en Java 8, y las funcionalidades que nos ofrece, comparado con la simple Future.
La clase CompletableFuture tiene mucha funcionalidad, por lo que he dividido en dos posts su explicación. Este primer post estará enfocado a hacer una introducción, y en el próximo post (y último de esta serie) veremos un uso más avanzado.

Esta serie consta de las siguientes partes:
Parte 1: Introducción
Parte 2: interfaz Future
Parte 3: CompletableFuture, introducción
Parte 4: CompletableFuture, uso avanzado

CompletableFuture: futuros «de verdad», gracias a Java 8
Con Java 8, entre otras muchas cosas, como los streams, lambdas, etc, se añadió la clase CompletableFuture (entre otras), con la que podemos en Java, por fin, tener unos futuros potentes, prácticamente al nivel de los de Scala. Es parecida funcionalmente a la clase ListenableFuture de la librería Guava.
Esta nueva clase implementa la interfaz Future que ya vimos, pero aporta muchísima más funcionalidad. Implementa también la nueva interfaz CompletionStage que es la que tiene todos los nuevos métodos.

Vamos a ver qué podemos hacer con esta clase. Tiene muchas posibilidades. Vamos a verlas directamente con ejemplos sencillos ¡Al lio!

Creación
Veamos tres ejemplos:

CompletableFuture<String> future = CompletableFuture.completedFuture("Prueba");
...
CompletableFuture<String> future = new CompletableFuture<>();
// other stuff
future.complete("Completado!");
...
CompletableFuture<Void> futureAsync = CompletableFuture.runAsync(() -> {
    // Some stuff...
});

Con la nueva clase CompletableFuture podemos crear el futuro ya directamente «completo», con el valor, con el método estático ‘completedFuture‘.
También podemos crearla directamente, con un new. Más adelante podemos simplemente «completar» el futuro, con el método ‘complete‘, como vemos en el segundo ejemplo.
Y, como es lógico, podemos crear un futuro que ejecute un pequeño proceso, como vemos en el tercer ejemplo, pasándole directamente una función lambda. En el usamos el método ‘runAsync‘, aunque tenemos más opciones (las veremos más adelante).

Variedad de métodos
Es importante explicar, para no liarse al ver el API de la clase CompletableFuture, que nos proporciona muchos métodos, casi todos ellos en tres «versiones». Por ejemplo, para el método ‘thenAccept‘ tenemos:

CompletableFuture<Void> thenAccept(Consumer<? super T> action);

CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action);

CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action, Executor executor);

– La versión sin «Async» ejecutará la función lambda en el mismo thread que el que la llama.
– La versión con «Async» ejecutará la ejecutara en un thread nuevo, usando para ello el Executor por defecto.
– La versión con «Async» y con parámetro ExecutorService, la ejecutará en un thread nuevo usando el Executor pasado como parámetro.

Lo normal si buscamos un buen sistema ‘reactivo’ sería usar siempre la versión con «Async», pero eso ya depende de cada situación. Por ejemplo, cuando encadenamos operaciones sobre un futuro, las siguientes puede ser lógico no usar las «Async» para que se sigan ejecutando en el miso thread que la anterior operación.

Métodos run y supply (y runAsync y supplyAsync)
Hemos visto que en la creación de CompletableFutures, la manera más común será pasándole una función lambda. Para ello tendremos dos métodos, ‘runAsync‘ y supplyAync‘ (y su correspondiente versión con parámetro ExecutorService).

CompletableFuture<Void> futureRunAsync = CompletableFuture.runAsync(() -> {
    LOGGER.info("Comenzando runAsync...");
    Sleep.sleepSeconds(3);
    LOGGER.info("Terminado runAsync!");
}, executor);

CompletableFuture<String> futureSupplyAsync = CompletableFuture.supplyAsync(() -> {
    LOGGER.info("Comenzando supplyAsync...");
    Sleep.sleepSeconds(3);
    LOGGER.info("Terminado supplyAsync!");
    return "Terminado";
}, executor);

Básicamente, estos métodos son equivalentes a lo que hacíamos en el post anterior para crear un Future, llamando al método ‘submit‘ del ExecutorService, pasándole nuestra lambda a ejecutar.

¿Cómo obtenemos el resultado?
Para obtener el resultado tenemos siempre la posibilidad de «bloquearnos» en el futuro, llamando al método ‘get‘ (CompletableFuture implementa también la interfaz Future).

Por ejemplo, para obtener el resultado del anterior futuro creado con ‘supplyAsync’:

LOGGER.info("Resultado bloqueando supplyAsync: " + futureSupplyAsync.get());

También nos ofrece un nuevo método ‘getNow‘ el cual lo que hace es, si el futuro se ha completado, devolver el resultado, y si no, devolver un parámetro que le pasamos a ese método.

Pero ninguna de esas opciones es lo que queremos. Ahora vamos a obtener el resultado sin bloquearnos, añadiendo algo así como un listener o callback a nuestro futuro.

CompletableFuture<String> futureSupplyAsync = CompletableFuture.supplyAsync(() -> {
    LOGGER.info("Comenzando supplyAsync...");
    Sleep.sleepSeconds(3);
    LOGGER.info("Terminado supplyAsync!");
    return "Terminado";
}, executor);

futureAsync.whenCompleteAsync((s, e) -> LOGGER.info("Resultado supplyAsync: " + s),
        executor);
LOGGER.info("Terminado main thread");

La llamada al método ‘whenCompleteAsync‘ realmente no se bloquea en el futuro. Lo que hace es «registrar» en el futuro que cuando se complete, ejecute esa función lambda.
El resultado por consola de este último ejemplo sería así:

19:07:34.030 [pool-1-thread-1] INFO CompletableFutureTest - Comenzando supplyAsync...
19:07:34.030 [main] INFO CompletableFutureTest - Terminado main thread
19:07:37.034 [pool-1-thread-1] INFO CompletableFutureTest - Terminado supplyAsync!
19:07:37.035 [pool-1-thread-2] INFO CompletableFutureTest - Resultado supplyAsync: Terminado

Como vemos, el último log «Terminado main thread» se muestra rapidamente, antes que el log que mostramos en la lambda del ‘whenCompleteAsync’.

Y hasta aquí por hoy. En el próximo post veremos a fondo el uso de callbacks, como este último, así como otras funcionalidades para combinar futuros.
Todo el código de los ejemplos está en mi github, proyecto completablefuture-example.