de en

Thorsten Reimers

Java CompletableFuture

10.09.2023

Ich habe mich lange nicht um "java.util.concurrent" gekümmert und Threads und Runnables selbst erzeugt und gestartet. Aber so geht es nicht weiter ...

Ich kann mir gut vorstellen, wofür man CompletableFuture benötigt und jetzt hatte ich auch einen Anwendungsfall: Knoten in einem JavaFX TreeView asynchron laden.

Also habe ich das Internet abgesucht und viel gelesen.

Gut, man verwendet CompletableFuture um einen Prozess zu starten, der im Hintergrund etwas berechnet, etwas lädt oder etwas ähnliches tut. Das macht man mit einem Supplier, der eine Methode bieten muss, die "get" heißt und das berechnete oder geladene Ergebnis liefert.

Dann kann man noch einen Consumer einsetzen, um das Ergebnis weiter zu verarbeiten. Dieser Consumer muss ebenfalls eine Methode "accept" besitzen, die das Ergebnis als Parameter erhält und aufgerufen wird, wenn der Supplier fertig ist.

Der Supplier mit seiner Methode "get" sieht so aus:

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

Als Consumer wird das Interface BiConsumer implementiert, das sowohl den Erfolgfall, als auch den Fehlerfall in seiner "accept"-Methode behandelt:

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

Die Main-Klasse sieht so aus:

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

Man kann auch alles in einer Klasse implementieren, was das Ergebnis noch einfacher und verständlicher macht, wie ich finde:

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