Optimal way to pass parameters into Supplier e.g. for CompletableFuture?

60 Views Asked by At

Sorry, I am pretty sure something like this must have been asked already, but I just can't find the answer.

Say, there's a service class ChessEngineService which has an instance method: String getBestMove(Query query); There's also a client of the service which currently making synchronous calls to the service as follows: String move = chessEngineService.getBestMove(query);

Because this call is blocking and can potentially take a long time execute, there's a need to make the invocation asynchronous e.g. using CompletableFuture: CompletableFuture.supplyAsync(chessEngineService::getBestMove).thenAccept(...)

The problem though, Supplier for supplyAsync() doesn't take any parameters since it's just a parameterless get().

I tried to introduce an instance variable for Query, a setter for it in ChessEngineService, and an overloaded version of getBestMove() with no parameters to use the aforementioned Query variable internally, but I don't like such a solution mainly because chessEngineService should remain stateless.

Would be grateful for better ideas.

1

There are 1 best solutions below

0
davidalayachew On

People enjoy method references (typeOrInstance::method) because they do almost everything a normal lambda can do, all while being (subjectively) cleaner, clearer, and easier to read/understand.

You have run headfirst into one of the few things they cannot do, but a normal lambda can do -- Method references cannot take in state from their local scope (unless you use the instance method reference form, more on that later).

So, understanding that, let's go back to your example.

You have a method getBestMove, and you want to call it on your instance chessEngineService (whose type is the same name). Specifically, you want to pass in your variable query.

Well, the easiest way to do it would be to use a normal lambda expression. Here is a reworking of your example.

CompletableFuture.supplyAsync(() -> chessEngineService.getBestMove(query)).thenAccept(...)

Now, because you are doing the normal lambda form (specifically, the lambda expression form), you CAN grab state from the local scope, and insert into your Supplier -- even though the Supplier takes no parameters.

This is one of the abilities of lambdas, and why there are multiple forms of them. The method reference form isn't just more concise, it communicates information -- a method reference form is not able to include any local state, other than listing the instance as the method provider. To clarify, if you have a method reference String::length, you are using the class name, and therefore, this is a pure, stateless lambda. Whereas, if you did it like this someString::length, the only state passed in is the instance someString.

Method references allow you to quickly realize that the lambda has exactly 0 or 1 piece of local state included in it, whereas a normal lambda can have as many as you choose to include (() -> Arrays.asList(local1, local2, local3, local4, etc)).