CompletableFuture | thenApply vs thenCompose


119

Non riesco a capire la differenza tra thenApply() ethenCompose() .

Quindi, qualcuno potrebbe fornire un caso d'uso valido?

Dai documenti Java:

thenApply(Function<? super T,? extends U> fn)

Restituisce un nuovo CompletionStageche, quando questa fase si completa normalmente, viene eseguita con il risultato di questa fase come argomento della funzione fornita.

thenCompose(Function<? super T,? extends CompletionStage<U>> fn)

Restituisce un nuovo CompletionStageche, quando questa fase si completa normalmente, viene eseguita con questa fase come argomento della funzione fornita.

Ho capito che il secondo argomento di thenComposeestende CompletionStage dove thenApplynon lo fa.

Qualcuno potrebbe fornire un esempio in quale caso devo usare thenApplye quando thenCompose?


39
Capisci la differenza tra mape flatMapin Stream? thenApplyè il maped thenComposeè il flatMapdi CompletableFuture. Usi thenComposeper evitare di avere CompletableFuture<CompletableFuture<..>>.
Misha

2
@Misha grazie per il tuo commento. Sì, conosco la differenza tra mape flatMape ho capito il tuo punto. Grazie ancora :)
GuyT

Questa è una guida molto carina per iniziare con CompletableFuture - baeldung.com/java-completablefuture
thealchemist

Risposte:


168

thenApply viene utilizzato se si dispone di una funzione di mappatura sincrona.

CompletableFuture<Integer> future = 
    CompletableFuture.supplyAsync(() -> 1)
                     .thenApply(x -> x+1);

thenComposeviene utilizzato se si dispone di una funzione di mappatura asincrona (ovvero una che restituisce a CompletableFuture). Quindi restituirà direttamente un futuro con il risultato, piuttosto che un futuro annidato.

CompletableFuture<Integer> future = 
    CompletableFuture.supplyAsync(() -> 1)
                     .thenCompose(x -> CompletableFuture.supplyAsync(() -> x+1));

14
Perché un programmatore dovrebbe usare .thenCompose(x -> CompletableFuture.supplyAsync(() -> x+1))invece di .thenApplyAsync(x -> x+1)? Essendo sincrona o asincrona è non la differenza rilevante.
Holger

17
Non lo farebbero così. Tuttavia, se una libreria di terze parti utilizzata restituisse un CompletableFuture, allora questo sarebbe thenComposeil modo per appiattire la struttura.
Joe C

1
@ ArunavSanyal, i voti mostrano un'immagine diversa. Questa risposta è chiara e concisa.
Alex Shesterov

@Holger leggi la mia altra risposta se sei confuso thenApplyAsyncperché non è quello che pensi che sia.
1283822

@ 1283822 Non so cosa ti fa pensare che ero confuso e non c'è nulla nella tua risposta a sostegno della tua affermazione che "non è quello che pensi che sia".
Holger

49

Penso che la risposta pubblicata da @Joe C sia fuorviante.

Vorrei provare a spiegare la differenza tra thenApplyethenCompose con un esempio.

Supponiamo di avere 2 metodi: getUserInfo(int userId)e getUserRating(UserInfo userInfo):

public CompletableFuture<UserInfo> getUserInfo(userId)

public CompletableFuture<UserRating> getUserRating(UserInfo)

Entrambi i tipi restituiti dal metodo sono CompletableFuture .

Vogliamo chiamare getUserInfo()prima, e al suo completamento, chiamare getUserRating()con il risultatoUserInfo .

Al termine del getUserInfo()metodo, proviamo entrambi thenApplye thenCompose. La differenza è nei tipi di restituzione:

CompletableFuture<CompletableFuture<UserRating>> f =
    userInfo.thenApply(this::getUserRating);

CompletableFuture<UserRating> relevanceFuture =
    userInfo.thenCompose(this::getUserRating);

thenCompose()funziona come quello di ScalaflatMap che appiattisce i futures annidati.

thenApply()ha restituito i futures annidati così come erano, ma ha thenCompose()appiattito l'annidato in CompletableFuturesmodo che sia più facile concatenarvi più chiamate di metodo.


2
Quindi la risposta di Joe C non è fuorviante. È corretto e più conciso.
koleS

2
Questo è stato molto utile :-)
dstibbe

1
Imho è un design scadente scrivere CompletableFuture <UserInfo> getUserInfo e CompletableFuture <UserRating> getUserRating (UserInfo) \\ invece dovrebbe essere UserInfo getUserInfo () e int getUserRating (UserInfo) se voglio usarlo async e chain, allora posso usa ompletableFuture.supplyAsync (x => getUserInfo (userId)). thenApply (userInfo => getUserRating (userInfo)) o qualcosa di simile, è più leggibile imho e non è obbligatorio racchiudere TUTTI i tipi di ritorno in CompletableFuture
user1694306

@ user1694306 Se si tratta di un design scadente o meno dipende dal fatto che la valutazione dell'utente sia contenuta nel UserInfo(quindi sì) o se debba essere ottenuta separatamente, forse anche costosa (quindi no).
glglgl

42

I Javadoc aggiornati in Java 9 probabilmente aiuteranno a capirlo meglio:

thenApply

<U> CompletionStage<U> thenApply​(Function<? super T,? extends U> fn)

Restituisce un nuovo CompletionStageche, quando questa fase si completa normalmente, viene eseguita con il risultato di questa fase come argomento della funzione fornita.

Questo metodo è analogo a Optional.mape Stream.map.

Vedere la CompletionStagedocumentazione per le regole relative al completamento eccezionale.

thenCompose

<U> CompletionStage<U> thenCompose​(Function<? super T,? extends CompletionStage<U>> fn)

Restituisce un nuovo CompletionStageche viene completato con lo stesso valore CompletionStagerestituito dalla funzione data.

Quando questa fase si completa normalmente, la funzione data viene invocata con il risultato di questa fase come argomento, restituendone un altro CompletionStage . Quando quella fase viene completata normalmente, il valore CompletionStagerestituito da questo metodo viene completato con lo stesso valore.

Per garantire il progresso, la funzione fornita deve predisporre l'eventuale completamento del suo risultato.

Questo metodo è analogo a Optional.flatMape Stream.flatMap .

Vedere la CompletionStagedocumentazione per le regole relative al completamento eccezionale.


14
Mi chiedo perché non abbiano nominato quelle funzioni mape flatMapin primo luogo.
Matthias Braun

1
@ MatthiasBraun Penso che sia perché thenApply()chiamerà semplicemente Function.apply(), ed thenCompose()è un po 'simile alle funzioni di composizione.
Didier L

14

thenApplye thenComposesono metodi di CompletableFuture. Usali quando intendi fare qualcosa per CompleteableFutureil risultato di con a Function.

thenApplyed thenComposeentrambi restituiscono un CompletableFuturecome risultato. Puoi concatenare più thenApplyo thenComposeinsieme. Fornire Functiona ogni chiamata, il cui risultato sarà l'ingresso alla successiva Function.

Il che Functionhai fornito a volte deve fare qualcosa in modo sincrono. Il tipo di ritorno del tuo Functiondovrebbe essere un non- Futuretipo. In questo caso dovresti usare thenApply.

CompletableFuture.completedFuture(1)
    .thenApply((x)->x+1) // adding one to the result synchronously, returns int
    .thenApply((y)->System.println(y)); // value of y is 1 + 1 = 2

Altre volte potresti voler eseguire l'elaborazione asincrona in questo Function. In tal caso dovresti usare thenCompose. Il tipo di ritorno del tuo Functiondovrebbe essere un file CompletionStage. Il successivo Functionnella catena otterrà il risultato di quello CompletionStagecome input, scartando così il file CompletionStage.

// addOneAsync may be implemented by using another thread, or calling a remote method
abstract CompletableFuture<Integer> addOneAsync(int input);

CompletableFuture.completedFuture(1)
    .thenCompose((x)->addOneAsync(x)) // doing something asynchronous, returns CompletableFuture<Integer>
    .thenApply((y)->System.println(y)); // y is an Integer, the result of CompletableFuture<Integer> above

Questa è un'idea simile a quella di Javascript Promise. Promise.thenpuò accettare una funzione che restituisce un valore o Promiseun valore. Il motivo per cui questi due metodi hanno nomi diversi in Java è dovuto alla cancellazione generica . Function<? super T,? extends U> fne Function<? super T,? extends CompletionStage<U>> fnsono considerati lo stesso tipo di Runtime - Function. Quindi thenApplye thenComposedevono essere denominati distintamente, altrimenti il ​​compilatore Java si lamenterebbe delle firme del metodo identiche. Il risultato finale è che Javascript Promise.thenè implementato in due parti - thenApplye thenCompose- in Java.

Puoi leggere la mia altra risposta se sei anche confuso su una funzione correlata thenApplyAsync.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.