Por experiencia te digo que manejar tareas asíncronas es como intentar armar un rompecabezas sin tener todas las piezas. Sí, es que es bastante complejo. De hecho yo terminaba frustrado cuando las operaciones bloqueaban el flujo principal de mi programa. Lo bueno fue que aprendí a manejar java.util.concurrent.CompletableFuture a tiempo, antes de volverme loco. Así que voy a dejarte esta guía para que entiendas cómo esta herramienta puede hacer que tus tareas asíncronas sean más fáciles de realizar.
¿Qué es java.util.concurrent.CompletableFuture?
¿Alguna vez has organizado una fiesta? Si es así, ya sabes que hay varias tareas que realizar como preparar comida, enviar invitaciones, decorar la casa. Una sola persona tendría que hacer tarea por tarea, ya que no las podría hacer todas al mismo tiempo. En cambio, si hay varias personas ayudando, se podrían realizar las tareas al mismo tiempo y, por ende, se terminaría más rápido el trabajo.
Java.util.concurrent.CompletableFuture funciona así. Esta clase de Java te deja ejecutar tareas de forma asíncrona, sin bloquear el hilo principal mientras esperas a que se completen. Lo que sucede es que esta clase tiene la capacidad de asignar tareas a diferentes hilos, por eso es que puedes continuar trabajando en otras cosas mientras esperas a que se terminen.
Entonces, ten en cuenta que CompletableFuture implementa las interfaces Future y CompletionStage y permite escribir código asíncrono y no bloqueante. Con ella, puedes ejecutar tareas en diferentes hilos, combinar resultados y manejar errores fácilmente.
Creando un CompletableFuture
Comencemos con lo básico: crear un CompletableFuture. Esto se logra fácilmente con el método estático supplyAsync, que ejecuta una tarea en segundo plano y devuelve su resultado.
import java.util.concurrent.CompletableFuture;
public class AsyncExample {
public static void main(String[] args) throws Exception {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// Simula una operación larga
return "¡Tarea completada!";
});
System.out.println(future.get()); // Resultado: ¡Tarea completada!
}
}
Aquí, la tarea asíncrona no bloquea el hilo principal mientras espera el resultado. Esto mejora significativamente el rendimiento.
Composición de tareas asíncronas
La verdadera magia de CompletableFuture está en su capacidad para combinar múltiples tareas. Supongamos que necesitas preparar un desayuno, pero no puedes servir el jugo hasta que el exprimidor haya terminado.
import java.util.concurrent.CompletableFuture;
public class TaskComposition {
public static void main(String[] args) throws Exception {
CompletableFuture<String> toast = CompletableFuture.supplyAsync(() -> "Tostadas listas");
CompletableFuture<String> juice = CompletableFuture.supplyAsync(() -> "Jugo servido");
CompletableFuture<String> breakfast = toast.thenCombine(juice, (t, j) -> t + " y " + j);
System.out.println(breakfast.get()); // Resultado: Tostadas listas y Jugo servido
}
}
En este ejemplo, dos tareas independientes (toast y juice) se combinan en una sola tarea que devuelve el desayuno completo.
Manejo de excepciones
En el mundo real, las cosas no siempre salen según lo planeado. Pero, ¿qué pasa si algo falla en tu tarea asíncrona? Pues mira cómo los métodos como exceptionally o handle se usan para gestionar errores.
import java.util.concurrent.CompletableFuture;
public class ExceptionHandling {
public static void main(String[] args) throws Exception {
CompletableFuture<Integer> result = CompletableFuture.supplyAsync(() -> {
// Simula un error
return 10 / 0;
}).exceptionally(ex -> {
System.out.println("Ocurrió un error: " + ex.getMessage());
return 42; // Valor por defecto
});
System.out.println(result.get()); // Resultado: 42
}
}
En este caso, cuando ocurre una excepción, exceptionally proporciona un valor de respaldo.
Otros métodos útiles de CompletableFuture
Además de los métodos que ya te expliqué, quiero dejarte otros que pueden ser muy útiles en tus proyectos:
- thenApply: Este te servirá para transformar el resultado de una tarea.
- thenAccept: Con este verás cómo se consume el resultado sin devolver un valor.
- allOf / anyOf: Gracias a este método podrás combinar múltiples tareas en una sola.
import java.util.concurrent.CompletableFuture;
public class MultipleTasks {
public static void main(String[] args) throws Exception {
CompletableFuture<Void> allTasks = CompletableFuture.allOf(
CompletableFuture.runAsync(() -> System.out.println("Tarea 1 completada")),
CompletableFuture.runAsync(() -> System.out.println("Tarea 2 completada")),
CompletableFuture.runAsync(() -> System.out.println("Tarea 3 completada"))
);
allTasks.get(); // Espera a que todas las tareas terminen
System.out.println("¡Todas las tareas han finalizado!");
}
}
Si trabajas en proyectos que requieren un alto rendimiento y escalabilidad, como sistemas distribuidos o aplicaciones web, java.util.concurrent.CompletableFuture te servirá bastante. Aquí te mostré que su capacidad para manejar flujos de trabajo complejos y errores lo hace ideal para aplicaciones modernas.
Ahora sabes que java.util.concurrent.CompletableFuture es más que una herramienta; es tu puerta de entrada al mundo de la programación asíncrona en Java. Si este tema te parece apasionante y quieres aprender a dominar herramientas avanzadas como esta, en el Bootcamp de Java Full Stack de KeepCoding te enseñamos todo lo necesario para convertirte en un desarrollador completo. ¡Inscríbete hoy y da el salto que tu carrera necesita!