So, this is version 2 of this post, because the old one was way too less explaining and way too much reinventing the wheel.
As I want to have a nice way to execute code asynchronously and have a callback of the result afterwards, I was happy to learn about lambdas , functional interfaces and method references
There are tons of functional interfaces coming with the lambdas. A lot of them, which can be used for method references, are in the java.util.function package.
So I want to have my Async Callback functionality the following way:
- I want to have a method without parameters, returning my expected Object.
This method needs to run asynchronously. - The asynchronous functionality will be provided by an ExecutorService, which executes
a Callable<T> object, and gets a Future<T>. - After the asynchronous method is finished, I want to execute the callback method, which receives
two parameters and returns nothing.- Parameter 1 will be an Exception, which will be the Exception coming from my asynchronous method.
(To be specific, it’s the Exception, if future.get() throws an exception) - Parameter 2 will be the object T,to be specific, the result from the asynchronous method.
- Parameter 1 will be an Exception, which will be the Exception coming from my asynchronous method.
Alright, let’s have a look into two functional interfaces we can use for our asynchronous call and for the callback.
Callable<T> is the functional interfaces we use for our asynchronous method. It returns an object of type T and receives no parameters.
These are three ways of calling a Callable<String> :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
import java.util.concurrent.Callable; public class Main{ public static void main(String[] args) { //old way Callable<String> c1 = new Callable<String>() { @Override public String call() throws Exception { return "Old way"; } }; //lambda / new way Callable<String> c2 = ()->{return "lambda way";}; //method reference Callable<String> c3 = Main::methodReference; try{ System.out.println(c1.call()); System.out.println(c2.call()); System.out.println(c3.call()); }catch(Exception e){ e.printStackTrace(); } } public static String methodReference(){ return "Method reference"; } } |
As you can see, the old way is to create an anonymous class and overwrite the call() method.
The new way with the lambda looks more intuitive, in my opinion. You create a lambda expression, which does not get any parameters () and in the execution code you just return a string.
For the method reference, you have to create a method, which itself has the same structure, as the functional interface would expect.
So the method structure has to return a String and must not receive any parameters: public static String methodName()
If the method fulfils the criteria, you can use the new double colon (::) syntax to let the compiler know which method you want to reference to.
Callable<String> c3 = Main::methodReference;
For the callback, we want to use an functional interface, which does not return anything (void) and receives two parameters.
The java.util.function package contains such an interface. It’s called BiConsumer<T,S>.
Again, three ways to use this interface.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
import java.util.function.BiConsumer; public class Main{ public static void main(String[] args) { //old way BiConsumer<Exception,String> b1 = new BiConsumer<Exception, String>() { @Override public void accept(Exception e, String s) { if(e != null){ e.printStackTrace(); } else{ System.out.println(s); } } }; //lambda / new way BiConsumer<Exception,String> b2 = (e,s)->{ if(e != null){ e.printStackTrace(); } else{ System.out.println(s); } }; //method reference BiConsumer<Exception,String> b3 = Main::methodReference; b1.accept(null, "Old Way"); b2.accept(null, "Lambda Way"); b3.accept(null, "Method Reference Way"); } public static void methodReference(Exception e, String s){ if(e != null){ e.printStackTrace(); } else{ System.out.println(s); } } } |
It’s kind of the same. Every way executes the same code, but the important part is how we can use the lambda and the method reference.
But how does this help us with our asynchronous callback problem?
Therefore we create a new method, which has 3 parameters.
- The first one is the ExecutorService, which we need to execute the code asynchronously.
- The second is the Callable<T> method reference, lambda or anonymous class.
- The third is the BiConsumer<Exception,T> method reference, lambda or anonymous class.
This method uses the ExecutorService to call a Runnable, in which we can execute our asynchronous call and wait for the result. And afterwards we can call the callback.
(We use the new lambda way to create the Runnable here)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public static <T> void execute(ExecutorService executor, Callable<T> callable, BiConsumer<Exception, T> callback){ executor.submit(()->{ //This is the lambda way to create a new Runnable //call callable asynchronously Future<T> future = executor.submit(callable); //create a new T instance T result = null; try { //get the result of the asynchronous call result = future.get(); //now call the callback with the result callback.accept(null,result); } catch (Exception e) { //if the asynchronous call throws an exception, call the callback with the exception callback.accept(e,null); } }); } |
Alright, so looking into the code we can see how everything works.
We create a Future<T> and call the callable asynchronously. We try to get the result of the future and execute the callback either with our error or with the result.
But how do we use this method? Have a look into the full listing, containing this method and one method for the callable as well as one for the callback. Those two methods will be passed with the method reference way.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.function.BiConsumer; public class Main{ public static ExecutorService executor; public static void main(String[] args) { //create the Executor executor = Executors.newFixedThreadPool(10); execute(executor, Main::methodReferenceCallable,Main::methodReferenceCallback); System.out.println("write into console, after execute was called"); } //will be executed asynchronously public static String methodReferenceCallable(){ System.out.println("do some calculations"); for(int i = 0; i < 10; i++){ try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } return "Method Reference Async result"; } //will be the callback public static void methodReferenceCallback(Exception e, String s){ if(e != null){ e.printStackTrace(); } else{ System.out.println(s); } //any way, we need to shutdown the ExecutorService executor.shutdown(); } public static <T> void execute(ExecutorService executor, Callable<T> callable, BiConsumer<Exception, T> callback){ executor.submit(()->{ //This is the lambda way to create a new Runnable //call callable asynchronously Future<T> future = executor.submit(callable); //create a new T instance T result = null; try { //get the result of the asynchronous call result = future.get(); //now call the callback with the result callback.accept(null,result); } catch (Exception e) { //if the asynchronous call throws an exception, call the callback with the exception callback.accept(e,null); } }); } } |
We now call our method asynchronously and get a feedback via our callback method. I think this listing should be self explaining.
Feel free to leave a comment below and ask any questions.
– Loki