de en

Thorsten Reimers

Java CompletableFuture

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();
    }
}