En Java 5 se añadió la interfaz Future para integrar la programación asíncrona. Sin embargo, esta interfaz no proporcionaba la posibilidad de combinar los resultados obtenidos al aplicar diferentes operaciones o manejar los posibles errores. Por este motivo, en Java 8 se incluyó la clase CompletableFuture, junto a la interfaz Future, la cual implementa también la interfaz CompletionStage. Esta interfaz incluye el contrato de operaciones para incluir la combinación de resultados procedentes de diferentes operaciones.
En las secciones posteriores se verán algunos de los usos propuestos por esta interfaz en Java 8.
A continuación se muestra un ejemplo de uso de la clase CompletableFuture:
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
public class Main {
public static void main(String args[]) throws InterruptedException, ExecutionException {
Future<String> completableFuture = AsynchronousAPI.helloWorldAsync();
String result = completableFuture.get();
System.out.println("The result is " + result);
}
}import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class AsynchronousAPI {
public static Future<String> helloWorldAsync() throws InterruptedException {
CompletableFuture<String> completableFuture
= new CompletableFuture<>();
Executors.newCachedThreadPool().submit(() -> {
Thread.sleep(1000);
completableFuture.complete("Hello world");
return null;
});
return completableFuture;
}
}En primer lugar, en la función main se obtiene la variable completableFuture, la cual es de tipo Future<String>, esta variable obtendrá su valor cuando la función helloWorldAsync() se resuelva.
A tráves de la operación completableFuture.get() se obtiene el resultado de la función y, posteriormente, se muestra en consola.
Por otro lado, el comportamiento de la función helloWorldAsyncse basa en el uso de la operación submit() de Executors.newCachedThreadPool(), que crea un nuevo hilo que se suspende durante 10 segundos para añadir un retardo temporal y, posteriormente, completa la operación con el valor Hello world.
En este caso, cuando finalice el procesamiento del programa se mostrará por consola el mensaje The result is Hello world.
A continuación, se muestra un ejemplo del uso del resultado devuelto en un CompletableFuture desde otra variable del mismo tipo:
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class Main {
public static void main(String args[]) throws InterruptedException, ExecutionException {
//Then apply
CompletableFuture<String> completableFuture =
AsynchronousAPI.helloWorldAsync();
CompletableFuture<String> future =
completableFuture.thenApply(s -> s + " world");
String result = future.get();
System.out.println(result);
//Then accept
completableFuture = AsynchronousAPI.helloWorldAsync();
CompletableFuture<Void> futureVoid =
completableFuture.thenAccept(s -> System.out.println(s + " world"));
futureVoid.get();
//Then run
completableFuture = AsynchronousAPI.helloWorldAsync();
futureVoid = completableFuture.thenRun(() -> System.out.println( "world"));
futureVoid.get();
}
}import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class AsynchronousAPI {
public static CompletableFuture<String> helloWorldAsync() throws InterruptedException {
CompletableFuture<String> completableFuture
= new CompletableFuture<>();
Executors.newCachedThreadPool().submit(() -> {
Thread.sleep(1000);
completableFuture.complete("Hello");
return null;
});
return completableFuture;
}
}En este caso, la función helloWorldAsync realiza el mismo comportamiento que en el ejemplo anterior, únicamente con la diferencia de que devuelve Hello y no Hello world completo.
Por otro lado, en la función main se utilizan tres funciones de la clase CompletableFuture. A continuación se detalla el comportamiento de cada una de ellas:
-
thenApply(): La variablefutureespera el resultado de la resolución de la variablecompletableFuture. En este caso, espera un resultado de tipoString. Finalmente, concatena a ese resultado la palabraworldy obtiene el valor completo de la cadena en su resolución, es decir,Hello world. -
thenAccept(): La variablefutureVoidespera el resultado de la resolución de la variablecompletableFuture, como en el caso anterior. La diferencia es que la variablefutureVoidno podrá devolver un resultado de tipoStringen su resolución, sino que será de tipoVoid. Por este motivo, se lanza directamente la funciónSystem.out.printlnen la resolución de la variablefutureVoid. -
thenRun(): La variablefutureVoiden este caso no espera ningún resultado, sino que sólo espera la resolución de la variablecompletableFuture. Como en el caso anterior, la variablefutureVoidlanzará directamente la funciónSystem.out.printlnen su resolución, pero no concatenará el resultado de la variablecompletableFuture, mostrando únicamente la palabraworld.
A continuación, se muestran varios fragmentos de código que ilustran la forma de combinar varios resultados procedentes de diferentes variables de tipo CompletableFuture:
CompletableFuture<String> completableFuture
= CompletableFuture.supplyAsync(() -> "Hello")
.thenCompose(s -> CompletableFuture.supplyAsync(() -> s + " World"));CompletableFuture<String> completableFuture
= CompletableFuture.supplyAsync(() -> "Hello")
.thenCombine(
CompletableFuture.supplyAsync(() -> " World"),
(s1, s2) -> s1 + s2)
);CompletableFuture future = CompletableFuture.supplyAsync(() -> "Hello")
.thenAcceptBoth(CompletableFuture.supplyAsync(() -> " World"),
(s1, s2) -> System.out.println(s1 + s2)
);En la sección anterior se utilizan tres funciones de la clase CompletableFuture para combinar los resultados. A continuación se detalla el comportamiento de cada una de ellas:
-
thenCompose(): Esta función permite concatenar el resultado de una variableCompletableFutureen otra una vez que haya finalizado su procesamiento. En este caso, la ejecución de losCompletableFutureno es independiente, sino que se encadenan de forma secuencial. -
thenCombine(): Esta función permite ejecutar losCompletableFuturede forma independiente y, posteriormente, combinar los resultados en la variableCompletableFutureresultante. -
thenAcceptBoth(): Esta función realiza el mismo procesamiento quethenCombine(), pero con la diferencia de que la variableCompletableFutureresultante (future) es de tipoVoidy realiza una acción. En este caso, invoca a la funciónSystem.out.println.
A continuación, se muestra un fragmento de código que ilustra la forma de combinar varios resultados procedentes de diferentes variables de tipo CompletableFuture, como en la sección anterior, pero realizando su procesamiento en paralelo:
CompletableFuture<String> future1
= CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2
= CompletableFuture.supplyAsync(() -> "Beautiful");
CompletableFuture<String> future3
= CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<Void> combinedFuture
= CompletableFuture.allOf(future1, future2, future3);
// ...
combinedFuture.get();En este caso, hay que tener en cuenta que el resultado de la función allOf de la clase CompletableFuture es de tipo Void y, por tanto, una limitación de este método es que no se pueden combinar los resultados de las variables future1, future2 y future3.
No obstante, haciendo uso del API para Streams de Java 8 se puede solventar este problema:
String combined = Stream.of(future1, future2, future3)
.map(CompletableFuture::join)
.collect(Collectors.joining(" "));Dados los siguientes fragmentos de código, responder a las siguientes preguntas:
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class AsynchronousAPI {
public static Future<Integer> additionAsync(List<Integer> elements) throws InterruptedException {
CompletableFuture<Integer> completableFuture
= new CompletableFuture<>();
Executors.newCachedThreadPool().submit(() -> {
//TO-DO
});
return completableFuture;
}
}import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
public class Main {
public static void main(String args[]) throws InterruptedException, ExecutionException {
List<Integer> elements = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Future<Integer> completableFuture = //TO-DO;
Integer result = //TO-DO;
System.out.println("The result is " + result);
}
}Complete las secciones TO-DO de las clases AsynchronousAPI y Main, teniendo en cuenta que:
- El método
additionAsyncdebe devolver la suma de todos los números contenidos enelements. - El método
additionAsyncdebe mostrar por consola cada uno de los elementos que esta sumando con el mensajeAdding (element). - El método
additionAsyncdebe añadir un retardo de 5 segundos en la suma de cada elemento. - En la función
mainse debe mostrar en consola el resultado de la suma completa con el mensajeThe result is (result).
Dado los siguientes fragmentos de código responder a las siguientes preguntas:
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
public class AsynchronousAPI {
public static CompletableFuture<Integer> additionAsync(List<Integer> elements) throws InterruptedException {
//TO-DO
}
public static CompletableFuture<Integer> mutiplicationAsync(List<Integer> elements) throws InterruptedException {
//TO-DO
}
}import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class Main {
public static void main(String args[]) throws InterruptedException, ExecutionException {
List<Integer> elements = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> elements2 = Arrays.asList(11, 12, 13, 14, 15, 16, 17, 18, 19, 20);
CompletableFuture<Void> completableFuture = //TO-DO;
completableFuture.get();
}
}- Complete las secciones TO-DO de las clases
AsynchronousAPIyMain, teniendo en cuenta que:
- El método
additionAsyncdebe devolver la suma de todos los números contenidos enelements. - El método
additionAsyncdebe mostrar por consola cada uno de los elementos que esta sumando con el mensajeAdding (element). - El método
additionAsyncdebe añadir un retardo de 2 segundos en la suma de cada elemento. - El método
multiplicationAsyncdebe devolver la producto de todos los números contenidos enelements2. - El método
multiplicationAsyncdebe mostrar por consola cada uno de los elementos que esta multiplicando con el mensajeMultiplying (element). - El método
multiplicationAsyncdebe añadir un retardo de 3 segundos en el producto de cada elemento. - En la función
mainse debe mostrar en consola el resultado de la suma completa con el mensajeThe result is (result). Este mensaje debe ser mostrado de forma directa cuando se complete el resultado de la variablecompletableFuture.
- Modifique el código de la clase
Mainpara que el procesamiento se realice en paralelo y se obtenga el mismo resultado por consola que en el apartado anterior.