Skip to Content
2026SOFT | Fortgeschrittenes und GUI (7-12)

SOFT | Fortgeschrittene Java-Themen und GUI-Programmierung (7-12)

Datum: September 2026
Lesedauer: 16 Minuten

Überarbeitet mit Claude Sonnet 5


Referenzmodul: Softwareprogrammierung (SOFT) - Dipl. Informatiker HF

Quellen:

  • Grundkurs Datenbankentwurf - Eine beispielorientierte Einführung für Studierende und Praktiker (4. Auflage)
    Helmut Jarosch, Springer Vieweg
  • Grundkurs Datenbankentwicklung - Von der Anforderungsanalyse zur komplexen Datenbankanfrage (5. Auflage)
    Stephan Kleuker, Springer Vieweg
  • Grundkurs JAVA - Von den Grundlagen bis zu Datenbank- und Netzanwendungen (12. Auflage)
    Dietmar Abts, Springer Vieweg
  • WISS Schulen für Wirtschaft Informatik Immobilien Unterricht Juni - September 2026

Block 07: Fortgeschrittene Java-Themen I

Internationalisierung

Internationalisierung trennt sprach- und regionsabhängige Darstellung vom Programmcode. Ein Locale besteht aus Sprach- und optionalem Regionscode. Datums-, Zahlen- und Währungsformatierung erfolgt über die passenden Java-APIs.

import java.text.NumberFormat; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.Locale; Locale locale = Locale.forLanguageTag("de-CH"); String date = LocalDate.now().format( DateTimeFormatter.ofPattern("dd. MMMM yyyy", locale)); String amount = NumberFormat.getCurrencyInstance(locale).format(1234.5);

Collator sortiert Text nach Sprachregeln. ResourceBundle lädt Übersetzungen aus sprachabhängigen Eigenschaften-Dateien. Texte gehören nicht als verstreute Literale in die Fachlogik.

Beispiele sind de-CH und en-US. Die Region beeinflusst Zahlen- und Währungsformate, die Sprache die Texte. Ein explizit übergebenes Locale macht Tests unabhängig von den Einstellungen des Rechners.

# messages_de.properties welcome=Willkommen

DecimalFormat formatiert Zahlen nach einem Muster. FormatStyle bietet lokalisierte Standardformate für Datum und Zeit. Sprach- und Regionsregeln sollten über Locale statt über selbst gebaute Trennzeichen oder Monatsnamen gesteuert werden.

DecimalFormat numberFormat = new DecimalFormat("#,##0.00"); String formattedNumber = numberFormat.format(1234567.5); DateTimeFormatter dateFormat = DateTimeFormatter .ofLocalizedDate(FormatStyle.FULL) .withLocale(Locale.forLanguageTag("de-CH"));

Generische Typen

Generics sichern Typen zur Compile-Zeit und vermeiden unnötige Casts. Übliche Typparameter sind T für Typ, E für Element, K für Schlüssel und V für Wert.

public class NumberBox<T extends Number> { private final T value; public NumberBox(T value) { this.value = value; } public T value() { return value; } }

NumberBox<Integer> und NumberBox<Double> sind gültig, NumberBox<String> nicht. Generics verwenden Referenztypen; primitive Werte werden bei Bedarf durch Wrapper repräsentiert.

Bei Methoden können Typparameter lokal eingeführt werden. Wildcards beschreiben flexible Grenzen: ? extends T ist eine Quelle zum Lesen, ? super T eine Senke zum Schreiben. Als Merksatz gilt PECS: Producer Extends, Consumer Super.

Collections

Iterable ermöglicht for-each. Collection umfasst insbesondere List und Set; Map<K,V> ist eine eigene Hierarchie für Schlüssel-Wert-Beziehungen.

TypTypische Eigenschaft
ArrayListschneller Indexzugriff, veränderliche Reihenfolge
LinkedListverkettete Struktur, selten die beste Standardwahl
HashSetkeine garantierte Reihenfolge, eindeutige Elemente
TreeSetsortierte, eindeutige Elemente
HashMapeindeutige Schlüssel, keine garantierte Reihenfolge
TreeMapsortierte Schlüssel
List<String> names = new ArrayList<>(); names.add("Anna"); names.add("Ben"); String first = names.get(0); Map<String, Integer> counts = new HashMap<>(); counts.put("Java", 2); for (Map.Entry<String, Integer> entry : counts.entrySet()) { System.out.println(entry.getKey() + ": " + entry.getValue()); }

Beim Entfernen während einer Iteration verwendet man den Iterator oder Methoden wie removeIf, nicht eine parallele Änderung der Collection im for-each-Rumpf.

Die Wahl der Collection ist eine fachliche und technische Entscheidung: List bewahrt eine Reihenfolge, Set erzwingt Eindeutigkeit, Map ordnet Schlüssel Werte zu. HashMap und HashSet benötigen konsistente equals/hashCode-Implementierungen. TreeMap und TreeSet benötigen eine natürliche Ordnung oder einen Comparator.

Services

Ein Servicevertrag ist ein Interface. Anbieter implementieren ihn, Konsumenten verwenden nur den Vertrag. ServiceLoader kann Implementierungen über Metadaten entdecken; in modularen Anwendungen werden dafür zusätzlich provides ... with ... und uses ... in module-info.java angegeben.

Bei einer klassischen Registrierung liegt unter META-INF/services eine Datei mit dem vollständig qualifizierten Namen des Interfaces. Jede Zeile nennt eine konkrete Implementierung. Der Konsument darf keine Implementierung direkt importieren, wenn Austauschbarkeit das Ziel ist.

Block 08: Fortgeschrittene Java-Themen II

Mengen und Mengenoperationen

Eine Menge enthält jedes Element höchstens einmal. x ∈ A bedeutet Zugehörigkeit, A ⊆ B bedeutet, dass jedes Element von A auch in B liegt. Die leere Menge enthält kein Element.

Für A = {1, 2} und B = {2, 3} gilt:

  • Schnitt: A ∩ B = {2}
  • Vereinigung: A ∪ B = {1, 2, 3}
  • Differenz: A \ B = {1}

Die Mengenschreibweise {x | P(x)} beschreibt alle Werte, für die das Prädikat P wahr ist.

Zwei Mengen sind gleich, wenn sie genau dieselben Elemente enthalten. Die Grundoperationen lassen sich auch programmatisch mit Set ausdrücken. In der Mathematik spielt die Reihenfolge keine Rolle; bei einer List dagegen schon. Diese Unterscheidung ist bei der Wahl eines Datentyps wesentlich.

Lambdas und funktionale Interfaces

Ein Lambda ist ein Ausdruck für eine Implementierung eines funktionalen Interfaces, also eines Interfaces mit genau einer abstrakten Methode.

Function<String, Integer> parse = Integer::parseInt; Function<Integer, Integer> square = value -> value * value; Function<String, Integer> parseAndSquare = parse.andThen(square); int result = parseAndSquare.apply("5");

Wichtige Interfaces aus java.util.function sind Predicate<T> für Prüfungen, Supplier<T> für Erzeugung, Consumer<T> für Verarbeitung, Function<T,R> für Abbildungen und BiFunction<T,U,R> für zwei Eingaben. UnaryOperator und BinaryOperator behalten den Eingabetyp bei.

Comparator<T> beschreibt eine Sortierreihenfolge. comparing und thenComparing bauen lesbare zusammengesetzte Vergleiche.

Methodenreferenzen wie String::toUpperCase sind Kurzformen für passende Lambdas. Ein Lambda darf lokale Variablen nur verwenden, wenn diese final oder effektiv final sind. Funktionale Interfaces können mit @FunctionalInterface dokumentiert werden; die Annotation lässt den Compiler die Ein-Methoden-Regel prüfen.

Streams

Ein Stream verarbeitet Daten als Pipeline. Zwischenoperationen wie filter, map, flatMap, sorted, limit und distinct sind faul. Eine Terminaloperation wie toList, count, reduce, forEach, anyMatch oder allMatch startet die Auswertung. Ein Stream wird nicht wiederverwendet.

List<String> result = names.stream() .filter(name -> name.length() >= 4) .map(String::toUpperCase) .sorted() .toList();

Streams eignen sich für deklarative Datenverarbeitung. Seiteneffekte in map oder filter erschweren Verständnis und Parallelisierung.

map verändert die Form jedes Elements, filter entfernt nicht passende Elemente und flatMap löst verschachtelte Strukturen auf. reduce fasst Werte zusammen; bei paralleler Ausführung muss der Akkumulator assoziativ und ohne unerlaubte Seiteneffekte sein. Ein Stream ist keine Collection und speichert die Ergebnisse nicht automatisch.

Block 09: Fortgeschrittene Java-Themen III

Optionale Werte

Optional<T> macht ein möglicherweise fehlendes Ergebnis im Rückgabetyp sichtbar. Optional.empty() steht für kein Ergebnis; of verlangt einen Nicht-Null-Wert, ofNullable akzeptiert beide Fälle.

Optional<Article> article = findById(4712); article.ifPresentOrElse( System.out::println, () -> System.out.println("Article not found"));

map, flatMap, filter und orElseGet erlauben die Verarbeitung ohne unkontrollierte Nullzugriffe. get() sollte nicht ohne vorherige Prüfung verwendet werden. Optional ist primär für Rückgabewerte gedacht, nicht für jedes Feld und jeden Parameter.

Dateisystem und Codierung

user.dir, user.home und java.io.tmpdir sind häufige System-Properties. Pfade werden mit Path.of(...) zusammengesetzt, nicht durch manuelle Trennzeichen. Files bietet unter anderem exists, createDirectory, copy, move und delete.

Ein Byte ist eine Speichereinheit, ein Zeichen ein Element eines Zeichensatzes. InputStream und OutputStream verarbeiten Bytes; Reader und Writer verarbeiten Zeichen. Bei Textdateien sollte die Codierung explizit angegeben werden.

try (BufferedReader reader = Files.newBufferedReader( Path.of("notes.txt"), StandardCharsets.UTF_8)) { String line; while ((line = reader.readLine()) != null) { System.out.println(line); } }

Dateien und Komprimierung

Für kleine Textdateien sind readString und writeString bequem; grössere Dateien werden zeilenweise oder als Stream verarbeitet.

Path input = Path.of("input.txt"); List<String> lines = Files.readAllLines(input, StandardCharsets.UTF_8); Files.writeString( Path.of("output.txt"), String.join(System.lineSeparator(), lines), StandardCharsets.UTF_8);

Buffered-Klassen reduzieren viele kleine Zugriffe. try-with-resources schliesst geöffnete Ressourcen zuverlässig. Für strukturierte Binärdaten stehen unter anderem DataInputStream/DataOutputStream, für Objekte ObjectInputStream/ObjectOutputStream zur Verfügung. Letztere dürfen nicht mit untrusted Daten verwendet werden.

java.util.zip bietet unter anderem GZIPInputStream und GZIPOutputStream. Komprimierung reduziert Speicher- und Netzbedarf, ersetzt aber keine Verschlüsselung.

XML und JSON

XML kann mit DOM eingelesen und über Transformer geschrieben werden. DOM lädt den Baum vollständig in den Speicher; bei grossen Dokumenten sind ereignisbasierte Verfahren geeigneter. Beim Parsen fremder XML-Daten müssen externe Entitäten und DTDs sicher konfiguriert werden.

Für JSON werden in Java häufig Jackson oder Gson verwendet. Ein Mapper wandelt JSON in Datenobjekte und zurück. Eingaben müssen validiert werden; unbekannte oder unerwartete Felder dürfen nicht unkritisch in sicherheitsrelevante Objekte übernommen werden.

Serialisierung wandelt einen Objektzustand in ein speicher- oder übertragbares Format um; Deserialisierung liest ihn zurück. Java-native Serialisierung über ObjectInputStream darf nicht auf untrusted Daten angewendet werden. JSON oder XML mit Schema- und Inhaltsprüfung ist in vielen Anwendungen kontrollierbarer.

Block 10: Fortgeschrittene Java-Themen IV

Nebenläufigkeit

Ein Prozess kann mehrere Threads enthalten. Runnable beschreibt eine auszuführende Aufgabe, Thread führt sie aus. In modernen Anwendungen werden Aufgaben meist an ExecutorService übergeben, statt Threads manuell zu verwalten.

ExecutorService executor = Executors.newFixedThreadPool(2); Future<Integer> future = executor.submit(() -> 21 * 2); int result = future.get(); executor.shutdown();

Ein Thread durchläuft Zustände wie neu, ausführbar, wartend und beendet. start() startet einen Thread genau einmal. sleep und join sind statische beziehungsweise wartende Operationen und sollten mit Thread.sleep(...) bzw. worker.join() aufgerufen werden. wait, notify und notifyAll gehören zum Monitor eines Objekts und werden nur innerhalb geeigneter Synchronisation verwendet.

Runnable liefert keinen Rückgabewert. Callable<T> kann dagegen einen Wert liefern und eine Exception auslösen; Future<T> repräsentiert das noch nicht verfügbare Ergebnis. ExecutorService verwaltet einen Pool und muss nach Gebrauch beendet werden. get() auf einem Future blockiert, bis das Ergebnis vorliegt.

Ein Thread kann durch sleep, blockierende Ein-/Ausgabe, ein Lock oder wait warten. interrupt() ist ein Signal für eine Unterbrechung und sollte nicht einfach ignoriert werden. Zeitkritische oder unabhängige Aufgaben eignen sich für Parallelität, kleine Aufgaben können durch Verwaltungsaufwand trotzdem langsamer werden.

Gemeinsame Ressourcen

Nebenläufigkeit macht Rennen um veränderlichen Zustand möglich. synchronized schützt kritische Abschnitte, Lock bietet zusätzliche Steuerung, atomare Klassen sichern einzelne Operationen. Unveränderliche Objekte und thread-sichere Collections reduzieren Risiken. Locks müssen in konsistenter Reihenfolge erworben werden, damit keine Deadlocks entstehen.

public class Account { private int balance; public synchronized void deposit(int amount) { if (amount < 0) { throw new IllegalArgumentException("Amount must not be negative"); } balance += amount; } public synchronized int balance() { return balance; } }

Bei Überweisungen müssen alle beteiligten Konten in einer stabilen Reihenfolge gesperrt werden. Fachliche Invarianten wie “kein negatives Guthaben” gehören in die zuständige Klasse.

Beispiel: sichere Überweisung

Eine Überweisung verändert zwei Konten. Werden die Konten in unterschiedlicher Reihenfolge gesperrt, können zwei Threads aufeinander warten und einen Deadlock bilden. Eine stabile Reihenfolge, zum Beispiel nach Konto-ID, verhindert diesen Zyklus.

public final class Account { private final int id; private int balance; public Account(int id, int balance) { this.id = id; this.balance = balance; } public int id() { return id; } public synchronized void deposit(int amount) { balance += amount; } public synchronized boolean withdraw(int amount) { if (amount > balance) { return false; } balance -= amount; return true; } } public final class TransferTask implements Runnable { private final Account source; private final Account target; private final int amount; public TransferTask(Account source, Account target, int amount) { this.source = source; this.target = target; this.amount = amount; } @Override public void run() { Account first = source.id() < target.id() ? source : target; Account second = first == source ? target : source; synchronized (first) { synchronized (second) { if (source.withdraw(amount)) { target.deposit(amount); } } } } }

Testen und JUnit

Tests zeigen, dass ein Fehler vorhanden sein kann; sie beweisen nicht die Fehlerfreiheit. Vollständige Eingabedeckung ist meist unmöglich. Tests sollen früh beginnen, reproduzierbar sein und Änderungen als Regressionstests absichern. Positive und negative Fälle sowie Grenzwerte gehören zur Auswahl.

  • Unit-Test: isolierte Klasse oder Funktion
  • Integrationstest: Zusammenarbeit mehrerer Bausteine
  • Systemtest: vollständiges System
  • Abnahmetest: Prüfung gegen fachliche Erwartungen
  • Regressionstest: Wiederholung bestehender Prüfungen nach Änderungen

JUnit Jupiter nutzt Annotationen wie @Test und @BeforeEach. Eine Testklasse benennt typischerweise die getestete Klasse mit dem Suffix Test. Testdaten und Abhängigkeiten werden möglichst unabhängig von externen Systemen gehalten; Stubs, Fakes oder Mocks helfen dabei.

Teststrategie

EntscheidungLeitfrage
TeststufeWird ein Modul, Zusammenspiel, Gesamtsystem oder die Abnahme geprüft?
TestautomatisierungWelche Prüfungen sollen bei jeder Änderung wiederholbar laufen?
TestperspektiveWird vom Quelltext (White Box) oder vom Verhalten (Black Box) ausgegangen?
TestfallartWelche positiven, negativen und grenzwertigen Eingaben sind nötig?
HilfsmittelBraucht es Debugger, Testdaten, Stub, Fake oder Mock?

Gute Testbarkeit entsteht durch klare Verantwortlichkeiten, kleine Abhängigkeiten, stabile Schnittstellen, nachvollziehbare Fehlerbehandlung und deterministisches Verhalten. Ein unabhängiger Tester entdeckt andere Fehler als die Person, die den Code geschrieben hat.

Ein guter Unit-Test hat eine klare Ausgangslage, genau eine wesentliche Fragestellung und ein überprüfbares Ergebnis. Grenzwerte, leere Eingaben, ungültige Eingaben und erwartete Exceptions decken typische Fehlerquellen ab. Testnamen sollen das erwartete Verhalten beschreiben, nicht die interne Implementierung.

assertThrows(IllegalArgumentException.class, () -> calculator.divide(4, 0));
import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; class CalculatorTest { @Test void addsTwoNumbers() { Calculator calculator = new Calculator(); assertEquals(7, calculator.add(3, 4)); } }

@BeforeEach und @AfterEach laufen vor beziehungsweise nach jedem Test. @BeforeAll und @AfterAll gelten für die gesamte Klasse. In Maven wird JUnit über eine Test-Dependency eingebunden; Engine und API sollten eine kompatible Version verwenden.

<dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>6.1.3</version> <scope>test</scope> </dependency>

Fehlersuche

Syntaxfehler melden Editor und Compiler. Logische Fehler werden über gezielte Testfälle, Reviews, Logs und Debugging sichtbar. Ein Debugger zeigt Zwischenzustände, Aufrufstapel und Kontrollfluss. Tests sollten die äussere Wirkung prüfen; Code-Review hilft zusätzlich, die innere Ursache zu finden.

Java-Module

Ein Modul besitzt einen Namen, deklariert Abhängigkeiten und exportiert nur ausgewählte Pakete. module-info.java macht diese Grenzen maschinenlesbar.

module mail.client { requires mail.api; }
module mail.api { exports ch.example.mail; }

Module stärken Kapselung und Konfiguration. Mit jlink kann daraus ein reduziertes Laufzeitimage entstehen.

Block 11: GUI-Programmierung mit Java I

Usability und Interaktion

Eine grafische Oberfläche verbindet fachliche Aufgaben mit visueller Darstellung und Interaktion. Gute Gestaltung beginnt mit Zielgruppen, Nutzungskontext und Anforderungen. Wireframes klären Anordnung, Mockups zeigen die visuelle Ausarbeitung, Prototypen erproben Interaktionen. Reviews und Usability-Tests liefern Rückmeldung vor und nach der Umsetzung.

Wichtige Gestaltungsbereiche sind Layout, Typografie, Farbe, Symbole, Internationalisierung, Personalisierung und unterschiedliche Bildschirmgrössen. Navigation, Eingaben, Ausgaben und Rückmeldungen müssen Zustände wie Laden, Erfolg und Fehler verständlich abbilden. Plattformrichtlinien wie Apple HIG, Material Design oder Microsoft Fluent liefern Konventionen, ersetzen aber keine Zielgruppenanalyse.

Ein Wireframe konzentriert sich auf Struktur und Navigation. Ein Mockup ergänzt die visuelle Gestaltung. Ein interaktiver Prototyp prüft Bedienabläufe, bevor Backend und vollständige Fachlogik vorhanden sind. Usability bedeutet unter anderem Effektivität, Effizienz, Verständlichkeit und Fehlertoleranz.

Java-GUI-Technologien

AWT stellt native Grundbausteine bereit, Swing erweitert sie um plattformübergreifende Widgets. SWT bindet sich stärker an das Betriebssystem. JavaFX bietet einen Scene Graph, CSS, FXML, Medien, Controls und die Anbindung an den Scene Builder. Für neue JavaFX-Projekte werden Java-Version, JavaFX-Version und Build-Abhängigkeiten gemeinsam verwaltet.

JavaFX-Grundmodell

Eine Anwendung erweitert Application. Die Methode start erhält die primäre Stage. Auf der Stage liegt eine Scene, deren Wurzelknoten weitere Knoten enthält. JavaFX führt UI-Änderungen im Application Thread aus; lange Aufgaben gehören in Hintergrunddienste und melden Ergebnisse über Platform.runLater zurück.

import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.control.Label; import javafx.scene.layout.StackPane; import javafx.stage.Stage; public class HelloFx extends Application { @Override public void start(Stage stage) { StackPane root = new StackPane(new Label("Hello, JavaFX!")); stage.setTitle("JavaFX example"); stage.setScene(new Scene(root, 320, 180)); stage.show(); } }

Der Ursprung des JavaFX-Koordinatensystems liegt oben links. X wächst nach rechts, Y nach unten. Layout-Panes berechnen Positionen automatisch; absolute Koordinaten sind vor allem für einfache Zeichnungen oder spezialisierte Szenen geeignet.

Häufige Layout-Panes sind HBox und VBox für Reihen, BorderPane für fünf Regionen, GridPane für tabellarische Anordnung, FlowPane für fliessende Inhalte und StackPane für Überlagerungen. AnchorPane erlaubt feste Abstände zu Kanten, ist für responsive Oberflächen aber sparsam einzusetzen.

Shapes und Gruppen

Linien, Rechtecke, Kreise und Ellipsen liegen in javafx.scene.shape. Füllung, Rand, Transparenz und Rotation sind Eigenschaften eines Knotens. Gruppen können Knoten verschachteln. Die Reihenfolge in der Szene beeinflusst, was sichtbar über anderem liegt.

ShapeKonstruktorprinzip
LineStart- und Endpunkt: startX, startY, endX, endY
Rectangleobere linke Ecke sowie Breite und Höhe
CircleMittelpunkt und Radius
EllipseMittelpunkt sowie X- und Y-Radius
Line line = new Line(10, 20, 300, 80); Rectangle rectangle = new Rectangle(30, 50, 200, 70); Circle circle = new Circle(100, 150, 40); Ellipse ellipse = new Ellipse(100, 50, 80, 30);
Circle sun = new Circle(70, 60, 30); sun.setFill(Color.GOLD); Rectangle ground = new Rectangle(0, 140, 320, 40); ground.setFill(Color.STEELBLUE); Group sceneContent = new Group(ground, sun);

Farben und Rückmeldungen

Color verwendet RGB-Anteile im Bereich 0 bis 1 oder Hilfsmethoden wie Color.rgb. Vordefinierte Farben sind für einfache Beispiele praktisch. In einer echten Anwendung sollten Farben zentral als Designwerte definiert werden, damit Kontrast, Theme und Barrierefreiheit überprüfbar bleiben.

Bei Color.rgb werden die Farbanteile als Werte von 0 bis 255 angegeben; bei Color.color als Werte von 0 bis 1. Transparenz kann als zusätzlicher Alpha-Wert gesetzt werden.

FarbeRGB
Color.BLACK0, 0, 0
Color.WHITE255, 255, 255
Color.CYAN0, 255, 255
Color.PINK255, 192, 203
Color.GRAY128, 128, 128
Color accent = Color.rgb(183, 44, 150); Color softGreen = Color.color(0.6, 0.85, 0.65);

JavaFX bietet Controls wie Button, TextField, TableView und Dialog. Ereignishandler reagieren auf Benutzeraktionen. Längere Datei-, Netzwerk- oder Berechnungsoperationen dürfen den UI-Thread nicht blockieren; dafür eignen sich Task oder Service.

Ein Ereignis wird von der Quelle ausgelöst und durch Handler verarbeitet. Controls besitzen Properties und Bindings, sodass sich Anzeigen automatisch an Zustände koppeln lassen. FXML trennt deklaratives Layout vom Controller; der Controller sollte nur UI-Aktionen koordinieren und die Fachlogik an Services delegieren.

button.setOnAction(event -> statusLabel.setText("Saved"));

Die Oberfläche sollte fachliche Logik nicht direkt enthalten. Eine Trennung von View, Zustand und Aktionen erleichtert Tests, Wartung und spätere Anpassungen.

JavaFX-Werkzeuge

Maven verwaltet JavaFX-Module und weitere Bibliotheken. Der Scene Builder unterstützt beim Erstellen von FXML-Dateien. Die IDE kann FXML mit dem Controller verbinden; Handlernamen und Modulzugriffe müssen dabei konsistent sein.

Für ein Projekt werden passende JavaFX-Module, eine Java-Version und ein Build-Plugin konfiguriert. Der Scene Builder wird separat installiert und in der IDE als Editor für FXML ausgewählt. Maven lädt die Abhängigkeiten aus einem Repository; ein Projekt-Reload macht neue Dependencies verfügbar.

Asynchrone Verarbeitung

UI-Code läuft im JavaFX Application Thread. Hintergrundarbeit wird ausgelagert und das Ergebnis danach kontrolliert auf den UI-Thread gebracht:

Task<String> task = new Task<>() { @Override protected String call() throws Exception { return Files.readString(Path.of("data.txt")); } }; task.setOnSucceeded(event -> textArea.setText(task.getValue())); new Thread(task).start();

Für eine einmalige Aktualisierung kann Platform.runLater(...) verwendet werden. Fehlerzustände, Abbruch und Fortschritt sollen in der Oberfläche sichtbar werden.

Bei Task gehören die eigentliche Arbeit in call() und UI-Anpassungen in setOnSucceeded, setOnFailed oder setOnCancelled. Fortschritt wird über updateProgress gemeldet. Tabellen und Diagramme sollten ihre Daten über beobachtbare Modelle beziehen, damit Änderungen gezielt in der Darstellung ankommen.

Block 12: GUI-Programmierung mit Java II

Diagramme mit JavaFX

JavaFX stellt fertige Chart-Klassen bereit, sodass Daten direkt aus Collections visualisiert werden, ohne dass Zeichenoperationen von Hand programmiert werden müssen. Alle Diagrammtypen erben von Chart. PieChart benötigt keine Achsen, da er Anteile eines Ganzen zeigt. Die übrigen Typen erben zusätzlich von XYChart und benötigen zwei Achsen, meist eine CategoryAxis (Kategorien) und eine NumberAxis (Werte).

DiagrammtypEignet sich für
PieChartAnteile eines Ganzen
BarChartVergleich diskreter Kategorien
LineChartEntwicklung über eine fortlaufende Grösse (z. B. Zeit)
AreaChartwie LineChart, zusätzlich mit gefüllter Fläche
ScatterChartZusammenhang zweier numerischer Grössen

Alle Charts erhalten ihre Daten über eine ObservableList. Ändert sich die Liste zur Laufzeit, aktualisiert sich die Darstellung automatisch, da JavaFX auf Properties und Bindings basiert.

Beispiel Kreisdiagramm (PieChart)

ObservableList<PieChart.Data> effortShare = FXCollections.observableArrayList( new PieChart.Data("Frontend", 40), new PieChart.Data("Backend", 35), new PieChart.Data("Database", 25)); PieChart effortChart = new PieChart(effortShare); effortChart.setTitle("Effort distribution");

Jeder PieChart.Data-Eintrag besteht aus einer Beschriftung und einem Zahlenwert; JavaFX berechnet den Anteil automatisch aus der Summe aller Werte.

Beispiel Balkendiagramm (BarChart)

CategoryAxis xAxis = new CategoryAxis(); NumberAxis yAxis = new NumberAxis(); BarChart<String, Number> salesChart = new BarChart<>(xAxis, yAxis); salesChart.setTitle("Quarterly sales"); XYChart.Series<String, Number> series2026 = new XYChart.Series<>(); series2026.setName("2026"); series2026.getData().add(new XYChart.Data<>("Q1", 120)); series2026.getData().add(new XYChart.Data<>("Q2", 150)); series2026.getData().add(new XYChart.Data<>("Q3", 90)); salesChart.getData().add(series2026);

Eine Series bündelt zusammengehörige Datenpunkte und erzeugt automatisch einen Legendeneintrag. Mehrere Serien im selben Chart werden nebeneinander (Balken) oder überlagert (Linien) dargestellt, was Vergleiche zwischen Kategorien oder Zeiträumen erleichtert.

Tabellen mit JavaFX (TableView)

TableView<T> zeigt eine Liste von Objekten des Typs T als Tabelle an. Jede Spalte ist ein TableColumn<T, V> und bestimmt über eine cellValueFactory, welche Eigenschaft eines Objekts sie anzeigt. Damit Änderungen automatisch in der Oberfläche erscheinen, verwenden die Datenklassen JavaFX-Properties (StringProperty, IntegerProperty, …) statt einfacher Felder.

public class Student { private final StringProperty name = new SimpleStringProperty(); private final IntegerProperty grade = new SimpleIntegerProperty(); public Student(String name, int grade) { this.name.set(name); this.grade.set(grade); } public StringProperty nameProperty() { return name; } public IntegerProperty gradeProperty() { return grade; } }
TableView<Student> table = new TableView<>(); TableColumn<Student, String> nameColumn = new TableColumn<>("Name"); nameColumn.setCellValueFactory(data -> data.getValue().nameProperty()); TableColumn<Student, Number> gradeColumn = new TableColumn<>("Grade"); gradeColumn.setCellValueFactory(data -> data.getValue().gradeProperty()); table.getColumns().addAll(nameColumn, gradeColumn); table.setItems(FXCollections.observableArrayList( new Student("Mira", 5), new Student("Ben", 4)));

Ein Klick auf einen Spaltenkopf sortiert die Tabelle automatisch anhand des Spaltentyps. Für editierbare Zellen wird table.setEditable(true) gesetzt und eine passende CellFactory verwendet, zum Beispiel TextFieldTableCell.forTableColumn(). Das Auswahlverhalten (Einzel- oder Mehrfachauswahl von Zeilen oder Zellen) wird über das SelectionModel der Tabelle gesteuert.