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