Java Anwendungsentwicklung – Tag 4 von 10
Von Franz-Martin, CTO bei Java Fleet Systems Consulting

Schwierigkeit: 🟡 Fortgeschritten
Dauer: ~6-8 Stunden
Voraussetzungen: Tag 1-3 abgeschlossen


🗺️ Deine Position im Kurs

TagThemaNiveauStatus
1Die Desktop-Ära: Warum GUIs?🟢 Grundlagen✅ Abgeschlossen
2AWT & Swing Grundlagen🟢 Grundlagen✅ Abgeschlossen
3Layouts & Event-Handling🟢 Grundlagen✅ Abgeschlossen
→ 4Komplexe Swing-Komponenten🟡 Fortgeschritten👉 DU BIST HIER!
5JavaFX: Die „moderne“ Alternative🔴 KOPFNUSS🔜 Kommt als nächstes
6JDBC Grundlagen🟢 Grundlagen🔒 Gesperrt
7JDBC Best Practices🟡 Fortgeschritten🔒 Gesperrt
8JPA Einführung🟢 Grundlagen🔒 Gesperrt
9JPA CRUD & Queries🟡 Fortgeschritten🔒 Gesperrt
10Integration & Ausblick🔴 KOPFNUSS🔒 Gesperrt

Legende: 🟢 Einsteiger-freundlich | 🟡 Erfordert Grundlagen | 🔴 Optional/Anspruchsvoll


⚡ Das Wichtigste in 30 Sekunden

Heute lernst du:

  • ✅ JTable für tabellarische Daten
  • ✅ Das Model-View-Konzept (wie React State!)
  • ✅ DefaultTableModel vs. eigenes TableModel
  • ✅ JTree für hierarchische Daten
  • ✅ Selection Handling und Sortierung

Die Kernregel:

Trenne IMMER Daten (Model) von Darstellung (View)!
Das macht dein Code testbar, wiederverwendbar und sauber.


🟢 GRUNDLAGEN: JTable

Das Model-View-Konzept

Swing-Komponenten

Abbildung 1: Model und View sind getrennt – wie in React!


JTable funktioniert nach dem Model-View-Prinzip:

  • Model (TableModel): Enthält die DATEN
  • View (JTable): Zeigt die Daten AN
// Model = Daten
DefaultTableModel model = new DefaultTableModel();
model.addColumn("Name");
model.addColumn("Alter");
model.addRow(new Object[]{"Max", 25});

// View = Darstellung
JTable table = new JTable(model);

Warum ist das gut?

  1. Daten ändern → View aktualisiert automatisch
  2. Model ist testbar ohne GUI
  3. Sortieren/Filtern ohne Daten zu ändern
  4. Eine Datenquelle, mehrere Views möglich

💡 React-Entwickler aufgepasst: Das ist genau wie State und Komponente! Der State (Model) hält die Daten, die Komponente (View) rendert sie.


JTable mit DefaultTableModel

// Spalten definieren
String[] spalten = {"ID", "Name", "Alter"};

// Model erstellen
DefaultTableModel model = new DefaultTableModel(spalten, 0);

// Zeilen hinzufügen
model.addRow(new Object[]{1, "Max", 25});
model.addRow(new Object[]{2, "Lisa", 30});

// Tabelle erstellen
JTable table = new JTable(model);

// WICHTIG: In ScrollPane packen (für Header!)
add(new JScrollPane(table));

Wichtige JTable-Konfiguration

// Einzel-Selektion
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

// Zeilenhöhe
table.setRowHeight(25);

// Spalten nicht verschiebbar
table.getTableHeader().setReorderingAllowed(false);

// Spaltenbreiten
table.getColumnModel().getColumn(0).setPreferredWidth(50);

// Automatische Sortierung aktivieren!
table.setAutoCreateRowSorter(true);

Selection Handling

table.getSelectionModel().addListSelectionListener(e -> {
    if (!e.getValueIsAdjusting()) {  // Nur bei finaler Auswahl
        int row = table.getSelectedRow();
        if (row >= 0) {
            // WICHTIG bei aktivem Sorter!
            int modelRow = table.convertRowIndexToModel(row);
            String name = (String) model.getValueAt(modelRow, 1);
            System.out.println("Ausgewählt: " + name);
        }
    }
});

⚠️ WICHTIG: Bei aktiviertem RowSorter ist die View-Zeile nicht gleich der Model-Zeile! Immer convertRowIndexToModel() verwenden!


🟡 PROFESSIONALS: Eigenes TableModel

Warum ein eigenes TableModel?

DefaultTableModel hat Nachteile:

  • Verwendet Object[][] – keine Typsicherheit
  • Schwer mit eigenen Klassen zu integrieren
  • Keine Kontrolle über Datentypen

Lösung: Eigenes TableModel von AbstractTableModel ableiten!

public class ProduktTableModel extends AbstractTableModel {
    
    private final List<Produkt> produkte = new ArrayList<>();
    private final String[] spalten = {"ID", "Name", "Preis"};
    
    // PFLICHT: Wie viele Zeilen?
    @Override
    public int getRowCount() {
        return produkte.size();
    }
    
    // PFLICHT: Wie viele Spalten?
    @Override
    public int getColumnCount() {
        return spalten.length;
    }
    
    // PFLICHT: Was steht in Zelle (row, col)?
    @Override
    public Object getValueAt(int row, int col) {
        Produkt p = produkte.get(row);
        return switch (col) {
            case 0 -> p.getId();
            case 1 -> p.getName();
            case 2 -> p.getPreis();
            default -> null;
        };
    }
    
    // OPTIONAL aber empfohlen: Spaltenname
    @Override
    public String getColumnName(int col) {
        return spalten[col];
    }
    
    // OPTIONAL aber WICHTIG: Datentyp pro Spalte
    @Override
    public Class<?> getColumnClass(int col) {
        return switch (col) {
            case 0, 1 -> String.class;
            case 2 -> Double.class;  // Für korrekte Sortierung!
            default -> Object.class;
        };
    }
    
    // OPTIONAL: Welche Zellen sind editierbar?
    @Override
    public boolean isCellEditable(int row, int col) {
        return col != 0;  // ID nicht editierbar
    }
    
    // Eigene Methode: Produkt hinzufügen
    public void addProdukt(Produkt p) {
        produkte.add(p);
        fireTableRowsInserted(produkte.size()-1, produkte.size()-1);
    }
}

Vorteile:

  • ✅ Typsicherer Zugriff: model.getProduktAt(row)
  • ✅ Korrekte Sortierung durch getColumnClass()
  • ✅ Boolean zeigt automatisch Checkbox!
  • ✅ Saubere Integration mit Domain-Objekten

🟢 GRUNDLAGEN: JTree

Hierarchische Daten darstellen

Abbildung 2: JTree für Baumstrukturen


// Wurzelknoten
DefaultMutableTreeNode root = 
    new DefaultMutableTreeNode("Projekt");

// Kindknoten
DefaultMutableTreeNode src = 
    new DefaultMutableTreeNode("src");
root.add(src);

// Blätter
src.add(new DefaultMutableTreeNode("Main.java"));
src.add(new DefaultMutableTreeNode("Utils.java"));

// JTree erstellen
JTree tree = new JTree(root);
add(new JScrollPane(tree));

Tree Selection Listener

tree.addTreeSelectionListener(e -> {
    DefaultMutableTreeNode node = 
        (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
    
    if (node == null) return;
    
    Object userObject = node.getUserObject();
    System.out.println("Ausgewählt: " + userObject);
});

Lazy Loading (für große Bäume)

// Platzhalter beim Erstellen
if (file.isDirectory()) {
    node.add(new DefaultMutableTreeNode("Laden..."));
}

// Beim Expandieren nachladen
tree.addTreeWillExpandListener(new TreeWillExpandListener() {
    @Override
    public void treeWillExpand(TreeExpansionEvent e) {
        DefaultMutableTreeNode node = 
            (DefaultMutableTreeNode) e.getPath().getLastPathComponent();
        // Echte Kinder laden...
    }
    
    @Override
    public void treeWillCollapse(TreeExpansionEvent e) {}
});

💬 Real Talk: Nova und die 10.000 Zeilen

Java Fleet Büro, Donnerstagmittag.


Nova: „Franz-Martin, meine Tabelle ist MEGA langsam. 10.000 Zeilen, und das Scrollen ruckelt.“

Franz-Martin: „Zeig mal den Code.“

Nova:

for (Produkt p : produkte) {
    Object[] row = {p.getId(), p.getName(), p.getPreis()};
    model.addRow(row);  // 10.000 mal!
}

Franz-Martin: „Da ist dein Problem. Jedes addRow() löst ein Event aus. 10.000 Events.“

Nova: „Und wie mache ich es richtig?“

Franz-Martin: „Zwei Optionen. Einfach: Alle Daten auf einmal setzen.“

Object[][] data = new Object[produkte.size()][3];
for (int i = 0; i < produkte.size(); i++) {
    data[i] = new Object[]{...};
}
model.setDataVector(data, spaltenNamen);

Nova: „Und die bessere Option?“

Franz-Martin: „Eigenes TableModel. Dann gibt’s gar keine Kopie der Daten – die Tabelle fragt direkt deine Liste.“

Nova: „Aber das ist mehr Aufwand…“

Franz-Martin: „Einmal. Danach ist es schneller, sauberer und du hast volle Kontrolle.“

Nova: „Okay, ich probier’s.“

Eine Stunde später.

Nova: „Wow. Von 5 Sekunden auf instant. Und der Code ist auch schöner.“

Franz-Martin: (grinst) „Willkommen im Club der Leute, die ihre eigenen TableModels schreiben.“


✅ Checkpoint

📝 Quiz

Frage 1: Warum sollte man JTable IMMER in ein JScrollPane packen?

A) Für bessere Performance
B) Damit der Header (Spaltennamen) angezeigt wird
C) Für automatische Sortierung
D) Ist nicht nötig


Frage 2: Was macht table.convertRowIndexToModel(viewRow)?

A) Konvertiert den View-Index zum Model-Index (wichtig bei Sortierung!)
B) Sortiert die Tabelle
C) Konvertiert Datentypen
D) Löscht eine Zeile


Frage 3: Warum ist getColumnClass() in einem eigenen TableModel wichtig?

A) Für die Spaltenbreite
B) Für korrekte Sortierung und Darstellung (Boolean → Checkbox!)
C) Für die Hintergrundfarbe
D) Ist optional und unwichtig


Frage 4: Was ist der Hauptvorteil von Model-View-Trennung?

A) Schnellere Ausführung
B) Daten und Darstellung sind unabhängig – testbar und wiederverwendbar
C) Weniger Code
D) Bessere Farben


📝 Quiz-Lösungen

Frage 1:B – Damit der Header angezeigt wird
Ohne JScrollPane fehlt der Tabellenkopf mit den Spaltennamen!

Frage 2:A – Konvertiert View-Index zu Model-Index
Bei aktiviertem Sorter zeigt Zeile 0 in der View nicht unbedingt Zeile 0 im Model!

Frage 3:B – Für korrekte Sortierung und Darstellung
Double sortiert numerisch (1, 2, 10), String alphabetisch (1, 10, 2). Boolean zeigt Checkbox!

Frage 4:B – Unabhängig, testbar, wiederverwendbar
Du kannst das Model ohne GUI testen. Mehrere Views können dasselbe Model nutzen.


🎨 Challenge

🟢 Level 1 – Einfache Tabelle

  • [ ] JTable mit 3 Spalten und 5 Beispielzeilen
  • [ ] Sortierung aktivieren
  • [ ] Bei Klick: Ausgewählte Zeile in Console ausgeben Zeit: 20-30 Minuten

🟡 Level 2 – CRUD-Tabelle

  • [ ] Zeilen hinzufügen, bearbeiten, löschen
  • [ ] Eingabefelder für neue Daten
  • [ ] Bestätigung vor Löschen Zeit: 45-60 Minuten

🔵 Level 3 – Custom TableModel

  • [ ] Eigene Klasse (z.B. Buch mit Titel, Autor, Jahr, Preis)
  • [ ] Eigenes TableModel implementieren
  • [ ] Boolean-Spalte „Gelesen“ mit Checkbox Zeit: 1-2 Stunden

📦 Downloads

ProjektInhaltDownload
java-anwendungsentwicklung-tag4.zipPersonenTabelle, DateiBrowser, CustomModel⬇️ Download

Quick Start:

mvn exec:java              # PersonenTabelle
mvn exec:java -Ptree       # DateiBrowser (JTree)
mvn exec:java -Pcustom     # Custom TableModel Demo

❓ FAQ

Wie mache ich Zellen nicht editierbar?
Im TableModel isCellEditable(row, col) überschreiben und false zurückgeben.

Meine Sortierung sortiert Zahlen falsch (1, 10, 2)?
getColumnClass() implementieren und Integer.class oder Double.class zurückgeben!

Wie färbe ich bestimmte Zeilen?
Eigenen TableCellRenderer implementieren. Fortgeschrittenes Thema!

JTree ist langsam bei vielen Knoten?
Lazy Loading implementieren – Kinder erst laden wenn Knoten expandiert wird.


🔗 Weiterführende Links

RessourceBeschreibung
How to Use TablesOracle Tutorial
How to Use TreesOracle Tutorial
Custom TableModelEigenes Model

🎉 Tag 4 geschafft!

Was du heute gelernt hast:

✅ JTable erstellen und konfigurieren
✅ Model-View-Trennung verstehen
✅ DefaultTableModel vs. eigenes TableModel
✅ JTree für hierarchische Daten
✅ Selection Handling und Sortierung

Morgen – Tag 5: JavaFX

Die „moderne“ Alternative zu Swing. CSS-Styling, FXML, Properties. Aber auch: Warum es sich nie durchgesetzt hat.


© 2025 Java Fleet Systems Consulting | java-developer.online

Autor

  • Franz-Martin

    65 Jahre alt, CTO und Gründer von Java Fleet Systems Consulting. Franz-Martin ist erfahrener Java-Entwickler, Tutor und Dozent, der das Unternehmen gegründet hat, um sein Wissen weiterzugeben und echte Java-Probleme zu lösen. Er moderiert Team-Diskussionen, mentoriert alle Crew-Mitglieder und sorgt dafür, dass technische Exzellenz mit Business-Realität kombiniert wird.