CodeGym /Corsi /JAVA 25 SELF /Combinare CompletableFuture: thenCombine, allOf, anyOf

Combinare CompletableFuture: thenCombine, allOf, anyOf

JAVA 25 SELF
Livello 55, Lezione 2
Disponibile

1. Combiniamo CompletableFuture

Nella vita reale raramente tutto arriva da un’unica fonte: il profilo dell’utente e i suoi ordini si possono caricare in parallelo, i dati di due microservizi — unire, e talvolta si vuole elaborare semplicemente la prima risposta arrivata. L’approccio sincrono costringe ad attendere in sequenza; la classe CompletableFuture consente di avviare tutto contemporaneamente e combinare i risultati in modo elegante. Fornisce metodi specifici per questo: thenCombine, allOf, anyOf. Vediamo ciascuno in ordine.

Attività parallele: due richieste asincrone

Come apparirebbe in modo sincrono:

String name = loadUserName();    // lento
int balance = loadUserBalance(); // lento
System.out.println("Nome: " + name + ", Saldo: " + balance);

Problema: la seconda chiamata inizierà solo dopo il completamento della prima.

Metodo asincrono

Con CompletableFuture è possibile avviare entrambe le attività contemporaneamente:

CompletableFuture<String> nameFuture = CompletableFuture.supplyAsync(() -> loadUserName());
CompletableFuture<Integer> balanceFuture = CompletableFuture.supplyAsync(() -> loadUserBalance());

Ma come ottenere entrambi i risultati e gestirli insieme? A questo serve thenCombine.

2. thenCombine: uniamo i risultati di due attività

Il metodo thenCombine consente di combinare due CompletableFuture ed eseguire un’azione quando entrambe le attività sono terminate. Restituisce un nuovo CompletableFuture con il risultato della combinazione.

Firma:

<A, B, C> CompletableFuture<C> thenCombine(
    CompletionStage<? extends B> other,
    BiFunction<? super A, ? super B, ? extends C> fn
)
  • A — tipo del risultato del primo future,
  • B — tipo del secondo,
  • C — tipo del risultato combinato.

Esempio

CompletableFuture<String> nameFuture = CompletableFuture.supplyAsync(() -> loadUserName());
CompletableFuture<Integer> balanceFuture = CompletableFuture.supplyAsync(() -> loadUserBalance());

CompletableFuture<String> resultFuture = nameFuture.thenCombine(
    balanceFuture,
    (name, balance) -> "Nome: " + name + ", Saldo: " + balance
);

resultFuture.thenAccept(System.out::println);

Come funziona:

  • Entrambi i future partono in parallelo.
  • Non appena entrambi sono terminati, viene invocata la funzione (name, balance) -> ....
  • Il future risultante contiene la stringa con il risultato.

Mini esempio con i numeri

CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(() -> 2);
CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(() -> 3);
CompletableFuture<Integer> sum = f1.thenCombine(f2, Integer::sum);

sum.thenAccept(result -> System.out.println("Somma: " + result));

Output:

Somma: 5

Variante asincrona

Se la combinazione è un’operazione pesante, usa thenCombineAsync:

f1.thenCombineAsync(f2, (a, b) -> a * b);

3. allOf: quando le attività sono molte

E se non avessimo due attività, ma una decina? Per esempio, vogliamo caricare in parallelo i dati di dieci utenti. Per questo esiste il metodo CompletableFuture.allOf.

Descrizione

CompletableFuture.allOf(f1, f2, ..., fn) restituisce un nuovo future che si completa quando si completano tutte le attività passate. Ma c’è una sfumatura: questo future non contiene alcun risultato — il suo tipo è sempre CompletableFuture<Void>. Per ottenere i risultati, bisogna separatamente «recuperarli» dai future originali.

Esempio

CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> "Primo");
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> "Secondo");

CompletableFuture<Void> all = CompletableFuture.allOf(f1, f2);

all.thenRun(() -> {
    // Tutte le attività sono terminate!
    String s1 = f1.join(); // join() — come get(), ma lancia un'eccezione unchecked
    String s2 = f2.join();
    System.out.println(s1 + " & " + s2);
});

Output:

Primo & Secondo

Esempio con un array di attività

List<CompletableFuture<String>> futures = new ArrayList<>();
for (int i = 0; i < 5; i++) {
    int id = i;
    futures.add(CompletableFuture.supplyAsync(() -> "Utente " + id));
}

CompletableFuture<Void> all = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));

all.thenRun(() -> {
    for (CompletableFuture<String> f : futures) {
        System.out.println(f.join());
    }
});

Cosa succede:

  • Tutte le attività vengono avviate in parallelo.
  • allOf si completerà quando tutte le attività saranno completate.
  • Nel blocco thenRun possiamo ottenere i risultati tramite join().

Schema visivo

[Future1]   \
[Future2] ----> [allOf] ---> thenRun
[Future3]   /

4. anyOf: attendiamo la prima attività che si completa

A volte non serve aspettare tutti, ma ottenere il risultato dell’attività più veloce. Ad esempio, chiediamo i dati a due server — useremo quelli di chi risponde per primo. Per questo esiste CompletableFuture.anyOf.

Descrizione

CompletableFuture.anyOf(f1, f2, ..., fn) restituisce un future che si completa quando si completa una qualsiasi delle attività passate. Il tipo del risultato è CompletableFuture<Object>, perché i tipi delle attività possono essere diversi.

Esempio

CompletableFuture<String> fast = CompletableFuture.supplyAsync(() -> {
    sleep(500);
    return "Server veloce";
});
CompletableFuture<String> slow = CompletableFuture.supplyAsync(() -> {
    sleep(2000);
    return "Server lento";
});

CompletableFuture<Object> any = CompletableFuture.anyOf(fast, slow);

any.thenAccept(result -> System.out.println("Ricevuto: " + result));

Output:

Ricevuto: Server veloce

Esempio con tipi diversi
Si possono combinare attività di tipi diversi, ma in tal caso il risultato sarà di tipo Object, e sarà necessario un cast esplicito.

5. Dettagli utili

Raccomandazioni

  • allOf non restituisce un array di risultati. Occorre mantenere i future originali per recuperarne i valori tramite join() o get().
  • anyOf restituisce il primo risultato completato, ma di tipo Object. Se tutte le attività sono dello stesso tipo, si può effettuare un cast.
  • Se una delle attività in allOf termina con errore — il future risultante terminerà anch’esso con errore.
  • Per thenCombine entrambe le attività devono terminare con successo, altrimenti — eccezione.

Tabella di confronto dei metodi

Metodo Quando usarlo Tipo di risultato
thenCombine
Devi unire i risultati di due attività Risultato della combinazione
allOf
Devi attendere il completamento di tutte le attività
Void
anyOf
Devi attendere il completamento di una qualsiasi attività Object (risultato della prima attività)

6. Errori tipici nella combinazione di CompletableFuture

Errore n. 1: Attendere il risultato tramite get()/join() nel thread principale.
Se scrivi codice asincrono ma alla fine chiami comunque get() o join(), blocchi il thread e perdi tutti i vantaggi dell’asincronia. È meglio usare thenAccept/thenRun per elaborare il risultato senza bloccare.

Errore n. 2: Non conservare i riferimenti ai future originali quando si usa allOf.
Se hai chiamato CompletableFuture.allOf(f1, f2, f3), ma non hai salvato f1, f2, f3 — non potrai ottenerne i risultati. allOf restituisce solo Void!

Errore n. 3: Non gestire gli errori nella catena.
Se una delle attività termina con errore, l’intero allOf o thenCombine terminerà anch’esso con errore. Usa i metodi di gestione degli errori (exceptionally, handle, whenComplete) per non perdere le eccezioni.

Errore n. 4: Incompatibilità di tipi con anyOf.
anyOf restituisce Object. Se i tuoi future restituiscono tipi diversi, dovrai capire cosa è arrivato per primo. Meglio usare tipi omogenei, se possibile.

Errore n. 5: Catene troppo complesse senza commenti.
Quando il codice cresce, le catene di future possono trasformarsi in «spaghetti». Non esitare a spezzare la catena in variabili separate e a commentare i passaggi.

1
Compito
JAVA 25 SELF, livello 55, lezione 2
Bloccato
Coordinamento delle consegne: attesa di tutti i carichi
Coordinamento delle consegne: attesa di tutti i carichi
1
Compito
JAVA 25 SELF, livello 55, lezione 2
Bloccato
Composizione del saluto: Unione di frammenti di saluto
Composizione del saluto: Unione di frammenti di saluto
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION