CompletableFuture | thenApply vs thenCompose

119

Ich kann den Unterschied zwischen thenApply() und nicht verstehen thenCompose().

Könnte jemand einen gültigen Anwendungsfall bereitstellen?

Aus den Java-Dokumenten:

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

Gibt eine neue zurück CompletionStage, die, wenn diese Stufe normal abgeschlossen ist, mit dem Ergebnis dieser Stufe als Argument für die angegebene Funktion ausgeführt wird.

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

Gibt eine neue zurück CompletionStage, die, wenn diese Phase normal abgeschlossen ist, mit dieser Phase als Argument für die angegebene Funktion ausgeführt wird.

Ich verstehe, dass das 2. Argument von thenComposedie CompletionStage erweitert, wo thenApplydies nicht der Fall ist.

Könnte jemand ein Beispiel geben, in welchem ​​Fall ich thenApplywann verwenden muss thenCompose?

GuyT
quelle
39
Verstehst du den Unterschied zwischen mapund flatMapin Stream? thenApplyist das mapund thenComposeist das flatMapvon CompletableFuture. Sie verwenden thenCompose, um zu vermeiden CompletableFuture<CompletableFuture<..>>.
Mischa
2
@ Mischa Danke für deinen Kommentar. Ja, ich kenne den Unterschied zwischen mapund flatMapund ich verstehe Ihren Standpunkt.
Nochmals vielen
Dies ist eine sehr schöne Anleitung, um mit CompletableFuture - baeldung.com/java-completablefuture
thealchemist

Antworten:

168

thenApply wird verwendet, wenn Sie eine synchrone Zuordnungsfunktion haben.

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

thenComposewird verwendet, wenn Sie eine asynchrone Zuordnungsfunktion haben (dh eine, die a zurückgibt CompletableFuture). Es wird dann eine Zukunft mit dem Ergebnis direkt zurückgeben und nicht eine verschachtelte Zukunft.

CompletableFuture<Integer> future = 
    CompletableFuture.supplyAsync(() -> 1)
                     .thenCompose(x -> CompletableFuture.supplyAsync(() -> x+1));
Joe C.
quelle
14
Warum sollte ein Programmierer .thenCompose(x -> CompletableFuture.supplyAsync(() -> x+1))anstelle von verwenden .thenApplyAsync(x -> x+1)? Synchron oder asynchron zu sein ist nicht der relevante Unterschied.
Holger
17
Das würden sie nicht so machen. Wenn jedoch eine von ihnen verwendete Bibliothek eines Drittanbieters a zurückgibt CompletableFuture, ist dies thenComposeder Weg, um die Struktur zu reduzieren.
Joe C
1
@ArunavSanyal, die Stimmen zeigen ein anderes Bild. Diese Antwort ist klar und prägnant.
Alex Shesterov
@ Holger las meine andere Antwort, wenn Sie verwirrt sind, thenApplyAsyncweil es nicht das ist, was Sie denken, dass es ist.
1283822
@ 1283822 Ich weiß nicht, warum Sie denken, dass ich verwirrt war, und Ihre Antwort enthält nichts, was Ihre Behauptung stützt, dass „es nicht das ist, was Sie denken“.
Holger
49

Ich denke, die Antwort von @Joe C ist irreführend.

Lassen Sie mich versuchen, den Unterschied zwischen thenApplyund thenComposeanhand eines Beispiels zu erklären .

Nehmen wir an, wir haben zwei Methoden: getUserInfo(int userId)und getUserRating(UserInfo userInfo):

public CompletableFuture<UserInfo> getUserInfo(userId)

public CompletableFuture<UserRating> getUserRating(UserInfo)

Beide Methodenrückgabetypen sind CompletableFuture.

Wir möchten getUserInfo()zuerst anrufen und nach Abschluss getUserRating()mit dem Ergebnis anrufen UserInfo.

getUserInfo()Versuchen wir nach Abschluss der Methode beide thenApplyund thenCompose. Der Unterschied liegt in den Rückgabetypen:

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

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

thenCompose()funktioniert wie Scala,flatMap die verschachtelte Futures abflacht.

thenApply() gab die verschachtelten Futures so zurück, wie sie waren, aber thenCompose() abgeflacht CompletableFutures, damit es einfacher ist, mehr Methodenaufrufe an sie zu ketten.

Dorjee
quelle
2
Dann ist die Antwort von Joe C nicht irreführend. Es ist richtig und prägnanter.
koleS
2
Das war sehr hilfreich :-)
dstibbe
1
Imho ist es schlechtes Design, CompletableFuture <UserInfo> getUserInfo und CompletableFuture <UserRating> getUserRating (UserInfo) \\ zu schreiben, stattdessen sollte es UserInfo getUserInfo () und int getUserRating (UserInfo) sein, wenn ich es asynchron und verketten möchte, dann kann ich Verwenden Sie ompletableFuture.supplyAsync (x => getUserInfo (userId)). thenApply (userInfo => getUserRating (userInfo)) oder ähnliches. Es ist imho besser lesbar und nicht zwingend erforderlich, ALLE Rückgabetypen in CompletableFuture
user1694306
@ user1694306 Ob es sich um ein schlechtes Design handelt oder nicht, hängt davon ab, ob die Benutzerbewertung im UserInfo(dann ja) enthalten ist oder ob sie separat eingeholt werden muss, möglicherweise sogar kostspielig (dann nein).
glglgl
42

Die aktualisierten Javadocs in Java 9 werden wahrscheinlich helfen, es besser zu verstehen:

dann anwenden

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

Gibt eine neue zurück CompletionStage, die, wenn diese Stufe normal abgeschlossen ist, mit dem Ergebnis dieser Stufe als Argument für die angegebene Funktion ausgeführt wird.

Diese Methode ist analog zu Optional.mapund Stream.map.

In der CompletionStageDokumentation finden Sie Regeln für außergewöhnliche Abschlüsse.

thenCompose

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

Gibt eine neue zurück CompletionStage, die mit demselben Wert abgeschlossen ist wie die CompletionStagevon der angegebenen Funktion zurückgegebene.

Wenn diese Phase normal abgeschlossen ist, wird die angegebene Funktion mit dem Ergebnis dieser Phase als Argument aufgerufen und eine andere zurückgegeben CompletionStage. Wenn diese Phase normal abgeschlossen ist, wird die CompletionStagevon dieser Methode zurückgegebene Phase mit demselben Wert abgeschlossen.

Um den Fortschritt sicherzustellen, muss die bereitgestellte Funktion den endgültigen Abschluss ihres Ergebnisses veranlassen.

Diese Methode ist analog zu Optional.flatMapund Stream.flatMap.

In der CompletionStageDokumentation finden Sie Regeln für außergewöhnliche Abschlüsse.

Didier L.
quelle
14
Ich frage mich , warum sie nicht die Funktionen benennen mapund flatMapan erster Stelle.
Matthias Braun
1
@MatthiasBraun Ich denke, das liegt daran, dass thenApply()es einfach aufgerufen Function.apply()wird und dem Erstellen von thenCompose()Funktionen ein bisschen ähnlich ist.
Didier L
14

thenApplyund thenComposesind Methoden von CompletableFuture. Verwenden Sie sie, wenn Sie beabsichtigen, mit a etwas zum CompleteableFutureErgebnis zu führen Function.

thenApplyund thenComposebeide geben a CompletableFutureals ihr eigenes Ergebnis zurück. Sie können mehrere thenApplyoder thenComposezusammen verketten. Geben Sie Functionfür jeden Anruf ein an, dessen Ergebnis die Eingabe für den nächsten sein wird Function.

Das von FunctionIhnen gelieferte muss manchmal etwas synchron machen. Der Rückgabetyp von Functionsollte ein Nicht- FutureTyp sein. In diesem Fall sollten Sie verwenden 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

In anderen Fällen möchten Sie möglicherweise eine asynchrone Verarbeitung durchführen Function. In diesem Fall sollten Sie verwenden thenCompose. Der Rückgabetyp Ihres Functionsollte a sein CompletionStage. Der nächste Functionin der Kette erhält das Ergebnis CompletionStageals Eingabe und wickelt so das aus 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

Dies ist eine ähnliche Idee wie bei Javascript Promise. Promise.thenkann eine Funktion akzeptieren, die entweder einen Wert oder Promiseeinen Wert zurückgibt . Der Grund, warum diese beiden Methoden in Java unterschiedliche Namen haben, liegt in der generischen Löschung . Function<? super T,? extends U> fnund Function<? super T,? extends CompletionStage<U>> fngelten als der gleiche Runtime-Typ - Function. Daher thenApplyund thenComposemüssen eindeutig benannt werden, sonst würde sich der Java-Compiler über identische Methodensignaturen beschweren. Das Endergebnis ist, dass Javascript Promise.thenin zwei Teilen implementiert ist - thenApplyund thenCompose- in Java.

Du kannst lesen meine andere Antwort , wenn Sie auch über eine verwandte Funktion verwirrt sind thenApplyAsync.

1283822
quelle