CompletableFuture | thenApply vs thenCompose


119

Não consigo entender a diferença entre thenApply() e thenCompose().

Então, alguém poderia fornecer um caso de uso válido?

Dos documentos Java:

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

Retorna um novo CompletionStageque, quando este estágio é concluído normalmente, é executado com o resultado desse estágio como o argumento para a função fornecida.

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

Retorna um novo CompletionStageque, quando este estágio é concluído normalmente, é executado com este estágio como o argumento da função fornecida.

Eu entendo que o segundo argumento de thenComposeestende o CompletionStage onde thenApplynão.

Alguém poderia dar um exemplo em que caso devo usar thenApplye quando thenCompose?


39
Você entende a diferença entre mape flatMapem Stream? thenApplyé o mape thenComposeé o flatMapde CompletableFuture. Você usa thenComposepara evitar ter CompletableFuture<CompletableFuture<..>>.
Misha

2
@Misha Obrigado pelo seu comentário. Sim, eu sei a diferença entre mape flatMape entendi. Obrigado novamente :)
GuyT

Este é um guia muito bom para começar com CompletableFuture - baeldung.com/java-completablefuture
thealchemist

Respostas:


168

thenApply é usado se você tiver uma função de mapeamento síncrono.

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

thenComposeé usado se você tiver uma função de mapeamento assíncrona (ou seja, uma que retorna a CompletableFuture). Em seguida, ele retornará um futuro com o resultado diretamente, em vez de um futuro aninhado.

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

14
Por que um programador deveria usar em .thenCompose(x -> CompletableFuture.supplyAsync(() -> x+1))vez de .thenApplyAsync(x -> x+1)? Ser síncrono ou assíncrono não é a diferença relevante.
Holger

17
Eles não fariam isso assim. No entanto, se uma biblioteca de terceiros que eles usaram retornasse um CompletableFuture, então este seria thenComposeo caminho para nivelar a estrutura.
Joe C

1
@ArunavSanyal, os votos mostram uma imagem diferente. Esta resposta é clara e concisa.
Alex Shesterov

@Holger leu minha outra resposta se estiver confuso thenApplyAsyncporque não é o que você pensa que é.
1283822

@ 1283822 Não sei o que te faz pensar que eu estava confuso e não há nada em sua resposta que apóie sua afirmação de que “não é o que você pensa que é”.
Holger

49

Acho que a resposta postada por @Joe C é enganosa.

Deixe-me tentar explicar a diferença entre thenApplye thenComposecom um exemplo.

Vamos supor que temos 2 métodos: getUserInfo(int userId)e getUserRating(UserInfo userInfo):

public CompletableFuture<UserInfo> getUserInfo(userId)

public CompletableFuture<UserRating> getUserRating(UserInfo)

Ambos os tipos de retorno de método são CompletableFuture.

Queremos chamar getUserInfo()primeiro e, ao concluir, chamar getUserRating()com o resultado UserInfo.

Na conclusão do getUserInfo()método, vamos tentar ambos thenApplye thenCompose. A diferença está nos tipos de retorno:

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

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

thenCompose()funciona como Scala,flatMap que nivela futuros aninhados.

thenApply()retornou os futuros aninhados como estavam, mas thenCompose()achatou os aninhados CompletableFuturespara que seja mais fácil encadear mais chamadas de método para ele.


2
Então, a resposta de Joe C não é enganosa. É correto e mais conciso.
koleS

2
Isso foi muito útil :-)
dstibbe

1
Mesmo assim, é um design pobre escrever CompletableFuture <UserInfo> getUserInfo e CompletableFuture <UserRating> getUserRating (UserInfo) \\ em vez disso, deveria ser UserInfo getUserInfo () e int getUserRating (UserInfo) se eu quiser usá-lo assíncrono e em cadeia, então posso use ompletableFuture.supplyAsync (x => getUserInfo (userId)). thenApply (userInfo => getUserRating (userInfo)) ou qualquer coisa assim, é mais legível imho, e não obrigatório envolver TODOS os tipos de retorno em CompletableFuture
user1694306

@ user1694306 Se é um design ruim ou não, depende se a avaliação do usuário está contida no UserInfo(então sim) ou se deve ser obtida separadamente, talvez até custosa (então não).
glglgl

42

Os Javadocs atualizados em Java 9 provavelmente ajudarão a entendê-lo melhor:

thenApply

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

Retorna um novo CompletionStageque, quando este estágio é concluído normalmente, é executado com o resultado desse estágio como o argumento para a função fornecida.

Este método é análogo a Optional.mape Stream.map.

Consulte a CompletionStagedocumentação para regras que abrangem a conclusão excepcional.

thenCompose

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

Retorna um novo CompletionStageque é concluído com o mesmo valor que o CompletionStageretornado pela função fornecida.

Quando esse estágio é concluído normalmente, a função fornecida é chamada com o resultado desse estágio como argumento, retornando outro CompletionStage. Quando esse estágio é concluído normalmente, o CompletionStageretornado por esse método é concluído com o mesmo valor.

Para garantir o progresso, a função fornecida deve providenciar a conclusão eventual de seu resultado.

Este método é análogo a Optional.flatMape Stream.flatMap.

Consulte a CompletionStagedocumentação para regras que abrangem a conclusão excepcional.


14
Eu me pergunto por que eles não nomearam essas funções mape flatMapem primeiro lugar.
Matthias Braun

1
@MatthiasBraun Acho que é porque thenApply()simplesmente chamará Function.apply(), e thenCompose()é um pouco semelhante às funções de composição.
Didier L

14

thenApplye thenComposesão métodos de CompletableFuture. Use-os quando quiser fazer algo para obter CompleteableFutureo resultado de a Function.

thenApplye thenComposeambos retornam a CompletableFuturecomo seu próprio resultado. Você pode encadear vários thenApplyou thenComposejuntos. Forneça um Functionpara cada chamada, cujo resultado será a entrada para a próxima Function.

O que Functionvocê forneceu às vezes precisa fazer algo de forma síncrona. O tipo de retorno de seu Functiondeve ser um não Futuretipo. Neste caso, você deve usar 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

Outras vezes, você pode querer fazer processamento assíncrono neste Function. Nesse caso, você deve usar thenCompose. O tipo de retorno de seu Functiondeve ser a CompletionStage. O próximo Functionna cadeia obterá o resultado disso CompletionStagecomo entrada, desembrulhando o 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

Esta é uma ideia semelhante à do Javascript Promise. Promise.thenpode aceitar uma função que retorna um valor ou Promisede um valor. A razão pela qual esses dois métodos têm nomes diferentes em Java é devido ao apagamento genérico . Function<? super T,? extends U> fne Function<? super T,? extends CompletionStage<U>> fnsão considerados o mesmo tipo de tempo de execução - Function. Portanto, thenApplye thenComposedeve ser nomeado de forma distinta, ou o compilador Java reclamaria sobre assinaturas de método idênticas. O resultado final é que o Javascript Promise.thené implementado em duas partes - thenApplye thenCompose- em Java.

Você pode ler minha outra resposta se também estiver confuso sobre uma função relacionada thenApplyAsync.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.