Sep 10, 2023
I did not care for "java.util.concurrent" for a long time and created Threads and Runnables by my own. But it is time now to change that ...
I can image where CompletableFuture can be helpful and got a use case now: loading tree items in a JavaFX TreeView asychronously.
So I started seeking the internet and reading descriptions.
Ok, CompletableFuture is used to start a background process, that calculates a result, loads some data or does anything similar. This is achieved using a Supplier, with a "get" method that calculates or loads the result.
Handling of the result is done by a Consumer. The consumer provides an "accept" method, that is called with the result as parameter when the Supplier is done.
The Supplier implements a "get" method:
import java.io.File;
import java.util.function.Supplier;
public class MySupplier implements Supplier<File[]> {
private File directory;
public MySupplier(File directory) {
this.directory = directory;
}
@Override
public File[] get() {
return directory.listFiles();
}
}
The Consumer implements the interface BiConsumer. The corresponding "accept" method is called in case of success or failure:
import java.io.File;
import java.util.function.BiConsumer;
public class MyConsumer implements BiConsumer<File[], Throwable> {
@Override
public void accept(File[] files, Throwable throwable) {
if (throwable != null)
throwable.printStackTrace();
else {
System.out.println("Found following files:");
for (File file : files) {
System.out.println(file.getName());
}
System.out.flush();
}
}
}
And this is the main class putting it all together:
import java.io.File;
import java.util.concurrent.CompletableFuture;
public class MainCompletable {
public void run() throws InterruptedException {
// get home directory
String directoryName = System.getProperty("user.home");
File directory = new File(directoryName);
// supplier used to read children of home directory
MySupplier mySupplier = new MySupplier(directory);
// consumer used to print out children of home directory
MyConsumer myConsumer = new MyConsumer();
// run async
CompletableFuture<File[]> completableFuture = CompletableFuture.supplyAsync(mySupplier);
completableFuture.whenComplete(myConsumer);
// wait until finished
while (!completableFuture.isDone())
Thread.sleep(50);
}
public static void main(String[] args) throws InterruptedException {
MainCompletable completable = new MainCompletable();
completable.run();
}
}
All of this can be implemented in one class only. To my opinion this makes the code easier and more understandable:
import java.io.File;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
public class MainCompletable2 implements Supplier<File []>, BiConsumer<File [], Throwable> {
private File directory;
@Override
public File[] get() {
return directory.listFiles();
}
@Override
public void accept(File[] files, Throwable throwable) {
if (throwable != null)
throwable.printStackTrace();
else {
System.out.println("Found following files:");
for (File file : files) {
System.out.println(file.getName());
}
System.out.flush();
}
}
public void run() throws InterruptedException {
// get home directory
String directoryName = System.getProperty("user.home");
directory = new File(directoryName);
// run async
CompletableFuture<File[]> completableFuture = CompletableFuture.supplyAsync(this);
completableFuture.whenComplete(this);
// wait until finished
while (!completableFuture.isDone())
Thread.sleep(50);
}
public static void main(String[] args) throws InterruptedException {
MainCompletable2 completable = new MainCompletable2();
completable.run();
}
}