Übungsaufgaben zur TableView: Mitfahrgesuche als Tabelle und Durchlaufzeiten

https://bildung.social/@oerinformatik/

https://oer-informatik.de/javafx-tableview-buchungen-uebungsaufgaben

JavaFX, TableView, CellValueFactory, CellFactory, FilteredList, SortedList, Filter, Kanban, Durchlaufzeit, Median, Übungsaufgaben, Prüfungsvorbereitung

tl/dr; (Bearbeitungszeit insgesamt 57 min, mehrere Aufgaben): Übungen zur TableView in der JavaFX-Desktop-App: Du ordnest CellValueFactory, CellFactory, Filter und Sortierung zu und sagst vorher, was beim Klick auf einen Spaltenkopf passiert, baust die Mitfahrgesuche als Tabelle mit Zellen und Sortierung, ergänzt einen Filter mit Notizsuche samt Tests und wertest die Durchlaufzeiten eines Kanban-Boards aus [AI-Assisted]. (Zuletzt geändert am 11.11.2026)

Dieser Artikel ist Teil einer Artikelreihe über eine Desktop-App mit JavaFX: Übersicht / Kanban-Projektstart / Projekt anlegen / FXML, Layouts und CSS / Properties, Bindings und MVVM / Backend anbinden / TableView für Buchungen / Formulare und Dialoge / Anmeldung mit Keycloak / Verwaltung und Diagramme / GUI-Tests mit TestFX / Abnahmetest und Kanban-Metriken / Paketieren mit jpackage

Diese Übungsaufgaben gehören zum Artikel TableView für Buchungen: anzeigen, filtern, sortieren und bestätigen. Sie spielen im Projekt Mitfahrlabor und führen den Strang der Mitfahrgesuche weiter: Das Gesuche-Fenster für Ticket JFX-13 „Offene Gesuche an meinem Haltepunkt anzeigen“ lädt seit den Übungen zum Anbinden des Backends die Gesuche im Hintergrund, zeigt sie aber nur als Liste. Jetzt bekommt es eine Tabelle und einen besseren Filter.

Überblick: Im BuchungenViewModel liegen alle Buchungen eines Angebots in einer ObservableList, eine FilteredList filtert sie, eine SortedList sortiert sie; die TableView zeigt die SortedList, ein Klick auf den Spaltenkopf ändert den Comparator der Tabelle, an den der Comparator der SortedList gebunden ist; je Spalte liefert die CellValueFactory den Wert und die CellFactory erzeugt die Zelle (mit KI erstellt)

Hinweise zur Bearbeitung: Bis zur Teil-1-Prüfung der IT-Berufe solltest Du die Basiskompetenz beherrschen, also Aufgabe 1.1. Als FIA, FIDP oder FIDV musst Du zur Teil-2-Prüfung Aufgaben auf Kernkompetenz-Ebene lösen können, das sind Aufgabe 2.1 und Aufgabe 2.2. Von FIA wird zusätzlich erwartet, dass ihnen Aufgaben auf Profilkompetenz-Ebene (Aufgabe 3.1) keine Probleme bereiten. Als Startcode für die Aufgaben 2.1 und 2.2 dient der Code-Stand src/05-backend-anbinden/uebung-mitfahrgesuche mit Deinem GesucheViewModel aus den letzten Übungen. Eine vollständige Beispiellösung mit allen Tests liegt unter src/06-tableview-buchungen/uebung-mitfahrgesuche; dort steht auch die Klasse FormatierteZelle aus dem Artikel. Code-Aufgaben löst Du am besten am Rechner und prüfst sie mit gradlew test (bzw. mvnw test). Die Operatoren (nennen, beschreiben, erläutern, beurteilen …) sind im Artikel Wie sind die Aufgaben aufgebaut? erklärt. Schreib Deine Lösung zuerst vollständig auf, bevor Du die Beispiellösung aufklappst.

Du möchtest eigene Aufgaben zum Üben von einem KI-Sprachmodell generieren lassen?

Kopiere den Text in ein KI-Sprachmodell Deiner Wahl und passe Kompetenzstufe, Anzahl, Bearbeitungszeit und Szenario bei Bedarf an. Wie die Aufgaben aufgebaut sind, beschreibt der Artikel Wie sind die Aufgaben aufgebaut?. Prüfe die Ergebnisse mit dem Lehrartikel: KI-Sprachmodelle machen Fehler.

Erstelle neue Übungsaufgaben zum Thema „TableView: Mitfahrgesuche als
Tabelle und Durchlaufzeiten“.

Bauplan: Halte Dich an Aufbau, Kompetenzstufen, Operatoren,
Anforderungsbereiche, Punkte und Zeitschätzung, die im Artikel
https://oer-informatik.de/aufgaben-aufbau beschrieben sind.

Fachliche Grundlage:
- https://oer-informatik.de/javafx-tableview-buchungen
- https://oer-informatik.de/szenario-mitfahrlabor
- https://oer-informatik.de/javafx-kanban-projektstart
- Stilbeispiel: https://oer-informatik.de/javafx-tableview-buchungen-uebungsaufgaben

Die Aufgaben sollen Inhalte zu diesen Schlagwörtern abdecken:
- JavaFX
- TableView
- CellValueFactory
- CellFactory
- FilteredList
- SortedList
- Filter
- Kanban
- Durchlaufzeit
- Median

Kompetenzstufe: Kernkompetenz (alternativ: Basiskompetenz, Profilkompetenz, Expertise)
Anzahl: 1 Aufgabe mit Teilaufgaben
Bearbeitungszeit in Minuten ca.: <5 bis 30 min>
Szenario: eines der Szenarien aus dem Bauplan

Vorgehen:
1. Lies zuerst die verlinkten Artikel. Kannst Du keine Webseiten
   abrufen, sag mir das, statt zu raten.
2. Steht bei einer Angabe noch ein Platzhalter in <…>, frag mich
   zuerst danach. Richte Punkte und Zahl der Teilaufgaben nach der
   Bearbeitungszeit (ca. 0,9 min je Punkt).
3. Gib je Aufgabe Titel mit Kompetenzstufe, Ausgangssituation und
   Teilaufgaben mit Operator und Punktzahl aus, dazu die geschätzte
   Bearbeitungszeit.
4. Gib die Beispiellösungen mit Punkteverteilung erst aus, wenn ich
   danach frage.

Aufgabe 1.1: Factorys, Filter und Sortierung zuordnen (Basiskompetenz)

(ca. 13 min Bearbeitungszeit)

Oksana Melnyk prüft JFX-05 in der Spalte „Test“ und möchte vorher wissen, ob Du die Bausteine der Tabelle auseinanderhalten kannst. Sie legt Dir sechs Wünsche und Codezeilen aus dem Gesuche-Fenster vor.

  1. Ordne jeder Zeile den Baustein zu, der dafür zuständig ist: CellValueFactory (CVF), CellFactory (CF), Prädikat der FilteredList (FL) oder Bindung des Comparators der SortedList (SL). (6 Punkte)
Welcher Baustein ist zuständig?
Nr. Wunsch oder Codezeile Baustein
1 spalteDatum.setCellValueFactory(z -> new ReadOnlyObjectWrapper<>(z.getValue().datum()))
2 Eine leere Notiz soll als grauer Strich „–“ erscheinen.
3 Ein Klick auf den Kopf der Spalte „frühestens“ soll die Gesuche nach dieser Uhrzeit ordnen.
4 Es sollen nur Gesuche mit der Richtung „zur Schule“ zu sehen sein.
5 setText(leer \|\| wert == null ? null : wert.format(DATUM));
6 Die Spalte „Haltepunkt“ soll den Namen des Haltepunkts aus dem Record Gesuch zeigen.
  1. Im Controller stehen nur diese beiden Zeilen, eine Bindung des Comparators fehlt. Beschreibe, was Ben sieht, wenn er auf den Kopf der Spalte „frühestens“ klickt, und gib die fehlende Zeile an. (3 Punkte)
gesucheTabelle.setItems(viewModel.getPassendeGesuche()); // eine SortedList ohne Comparator
gesucheTabelle.getSortOrder().add(spalteFruehestens);
  1. Ein Teammitglied lässt die CellValueFactory der Spalte „Datum“ gleich den fertigen Text liefern: z -> new ReadOnlyStringWrapper(z.getValue().datum().format(DATUM)). Die Anzeige sieht danach genauso aus. Beschreibe, was beim Sortieren nach der Spalte mit den Gesuchen vom 09.11.2026 und vom 15.10.2026 passiert, und erläutere, wie Wert und Darstellung besser getrennt werden. (3 Punkte)

  2. Nenne zwei Gründe, warum das Team die CellValueFactory als Lambda schreibt und nicht als new PropertyValueFactory<>("datum"). (2 Punkte)

  1. Je Zeile 1 Punkt:
Welcher Baustein ist zuständig? (Lösung)
Nr. Wunsch oder Codezeile Baustein
1 setCellValueFactory(…datum()) CVF: liefert den Wert der Zeile für die Spalte
2 leere Notiz als grauer Strich CF: Darstellung in updateItem, Text „–“ und CSS-Klasse
3 Klick auf „frühestens“ ordnet SL: Comparator der SortedList an tabelle.comparatorProperty() gebunden
4 nur „zur Schule“ FL: Prädikat der FilteredList
5 setText(… wert.format(DATUM)) CF: Zeile aus updateItem einer eigenen Zelle
6 Name des Haltepunkts CVF: z -> new ReadOnlyStringWrapper(z.getValue().haltepunkt())
  1. Ben sieht keine Änderung der Reihenfolge (1 Punkt): Die Tabelle versucht zu sortieren, stellt fest, dass sie eine SortedList mit ungebundenem Comparator nicht selbst umsortieren darf, setzt die Sortierung zurück (der Pfeil im Spaltenkopf verschwindet wieder) und schreibt nur einen Hinweis ins Log (1 Punkt). Die fehlende Zeile (1 Punkt):
viewModel.getPassendeGesuche().comparatorProperty().bind(gesucheTabelle.comparatorProperty());
  1. Die Spalte sortiert dann Texte zeichenweise: „09.11.2026“ steht vor „15.10.2026“, obwohl der 15. Oktober früher ist (1 Punkt). Besser liefert die CellValueFactory den Wert LocalDate (danach sortiert die Tabelle) (1 Punkt), und eine CellFactory, etwa new FormatierteZelle<>(d -> d.format(DATUM)), formatiert nur die Anzeige (1 Punkt).

  2. Je Grund 1 Punkt, z. B.:

  • Der Compiler prüft Methode und Typ; ein Tippfehler im Namen fiele bei PropertyValueFactory erst zur Laufzeit als leere Spalte auf.
  • PropertyValueFactory arbeitet per Reflexion; im Modulsystem müsste das Paket des Models für javafx.base geöffnet werden.
  • Ein Lambda kann auch abgeleitete Werte liefern, etwa nur die Uhrzeit oder einen zusammengesetzten Text.

Aufgabe 2.1: Die Mitfahrgesuche als Tabelle anzeigen (Kernkompetenz)

(ca. 15 min Bearbeitungszeit)

Aylin Kaya testet das Gesuche-Fenster und wünscht sich im Replenishment: „Ich will die Gesuche nach Uhrzeit sortieren und sehen, ob jemand eine Notiz geschrieben hat, ohne jede Zeile zu lesen.“ Das Team ersetzt die ListView durch eine Tabelle.

  1. Ersetze in gesuche.fxml die ListView #gesucheListe durch eine TableView #gesucheTabelle mit den Spalten „Datum“, „frühestens“, „spätestens“, „Haltepunkt“ und „Notiz“ (fx:id z. B. spalteDatum). Die Spalten sollen die ganze Breite ausfüllen; ohne Treffer zeigt die Tabelle „Keine offenen Gesuche für diese Auswahl.“ (4 Punkte)

  2. Ergänze im GesucheController die Spaltenfelder mit passenden Typparametern und setze für alle fünf Spalten eine CellValueFactory. Das Datum erscheint im Format 16.11.2026, eine leere Notiz als grauer Strich „–“ (CSS-Klasse ohne-notiz). Achte darauf, dass Zellen wiederverwendet werden. (6 Punkte)

  3. Sorge dafür, dass ein Klick auf einen Spaltenkopf sortiert und die Tabelle anfangs nach „frühestens“ aufsteigend sortiert ist. Der feste Comparator im GesucheViewModel entfällt dafür. (3 Punkte)

  4. Passe den Controller-Test an: Er prüft, dass die Tabelle fünf Spalten hat und die Gesuche nach dem Laden in der Reihenfolge ihrer frühesten Uhrzeit zeigt. (3 Punkte)

  1. Tabelle in gesuche.fxml (Importe für TableView und TableColumn ergänzen; Spalten 2 Punkte, Breite und Platzhalter je 1 Punkt):
<TableView fx:id="gesucheTabelle" VBox.vgrow="ALWAYS">
    <columnResizePolicy>
        <TableView fx:constant="CONSTRAINED_RESIZE_POLICY_FLEX_LAST_COLUMN"/>
    </columnResizePolicy>
    <placeholder>
        <Label text="Keine offenen Gesuche für diese Auswahl."/>
    </placeholder>
    <columns>
        <TableColumn fx:id="spalteDatum" text="Datum" prefWidth="95"/>
        <TableColumn fx:id="spalteFruehestens" text="frühestens" prefWidth="85"/>
        <TableColumn fx:id="spalteSpaetestens" text="spätestens" prefWidth="85"/>
        <TableColumn fx:id="spalteHaltepunkt" text="Haltepunkt" prefWidth="200"/>
        <TableColumn fx:id="spalteNotiz" text="Notiz" prefWidth="160"/>
    </columns>
</TableView>
  1. Felder und Zellen im Controller (Typparameter 1 Punkt, CellValueFactorys 2 Punkte, Datum 1 Punkt, Notiz-Zelle mit leerem Fall und Entfernen der Klasse 2 Punkte):
private static final DateTimeFormatter DATUM = DateTimeFormatter.ofPattern("dd.MM.yyyy");

@FXML private TableView<Gesuch> gesucheTabelle;
@FXML private TableColumn<Gesuch, LocalDate> spalteDatum;
@FXML private TableColumn<Gesuch, LocalTime> spalteFruehestens;
@FXML private TableColumn<Gesuch, LocalTime> spalteSpaetestens;
@FXML private TableColumn<Gesuch, String> spalteHaltepunkt;
@FXML private TableColumn<Gesuch, String> spalteNotiz;

private void tabelleEinrichten() {
    // CellValueFactory: welcher Wert aus dem Gesuch in die Spalte kommt
    spalteDatum.setCellValueFactory(z -> new ReadOnlyObjectWrapper<>(z.getValue().datum()));
    spalteFruehestens.setCellValueFactory(z -> new ReadOnlyObjectWrapper<>(z.getValue().fruehestens()));
    spalteSpaetestens.setCellValueFactory(z -> new ReadOnlyObjectWrapper<>(z.getValue().spaetestens()));
    spalteHaltepunkt.setCellValueFactory(z -> new ReadOnlyStringWrapper(z.getValue().haltepunkt()));
    spalteNotiz.setCellValueFactory(z -> new ReadOnlyStringWrapper(z.getValue().notiz()));

    // CellFactory: Darstellung; sortiert wird weiter nach LocalDate
    spalteDatum.setCellFactory(spalte -> new FormatierteZelle<>(datum -> datum.format(DATUM)));
    spalteNotiz.setCellFactory(spalte -> new TableCell<>() {
        @Override
        protected void updateItem(String notiz, boolean leer) {
            super.updateItem(notiz, leer);
            getStyleClass().remove("ohne-notiz");   // Zelle kann vorher eine leere Notiz gezeigt haben
            if (leer || notiz == null) {
                setText(null);
            } else if (notiz.isBlank()) {
                setText("–");
                getStyleClass().add("ohne-notiz");
            } else {
                setText(notiz);
            }
        }
    });
    // … Sortierung siehe c)
}

In gesuche.css:

.table-cell.ohne-notiz {
    -fx-text-fill: #8a8a8a;
}
  1. Im ViewModel entsteht die SortedList ohne Comparator, und der Getter liefert den Typ SortedList, damit der Controller den Comparator binden kann (1 Punkt). Im Controller (Bindung 1 Punkt, Startsortierung 1 Punkt):
// GesucheViewModel
private final SortedList<Gesuch> passend = new SortedList<>(gefiltert);

public SortedList<Gesuch> getPassendeGesuche() {
    return passend;
}

// GesucheController.tabelleEinrichten()
gesucheTabelle.setItems(viewModel.getPassendeGesuche());
viewModel.getPassendeGesuche().comparatorProperty().bind(gesucheTabelle.comparatorProperty());
gesucheTabelle.getSortOrder().add(spalteFruehestens);
  1. Test (Laden abwarten 1 Punkt, Prüfungen je 1 Punkt). Mit dem Filter aus Aufgabe 2.2 zeigt die Tabelle nach dem Laden die drei Gesuche „zur Schule“; ohne Aufgabe 2.2 wählst Du im Test vorher Haltepunkt und Datum:
@Test
@SuppressWarnings("unchecked")
void tabelleZeigtGesucheZurSchuleNachFruehestensSortiert() throws Exception {
    Parent wurzel = ladeGesuche();   // lädt gesuche.fxml mit Beispielgesuchen und wartet auf den Task
    TableView<Gesuch> tabelle = (TableView<Gesuch>) wurzel.lookup("#gesucheTabelle");

    assertEquals(5, tabelle.getColumns().size());
    assertEquals(List.of(3L, 1L, 5L), tabelle.getItems().stream().map(Gesuch::id).toList());
}

Gesuch 3 beginnt um 06:45 Uhr, Gesuch 1 und 5 um 07:00 Uhr; Gesuch 1 steht vor 5, weil SortedList bei gleichen Werten die Reihenfolge der Quelle behält.

Aufgabe 2.2: Die Mitfahrgesuche filtern (Kernkompetenz)

(ca. 13 min Bearbeitungszeit)

Mit der Tabelle wird der Filter zum Hindernis: Solange Haltepunkt und Datum nicht gewählt sind, bleibt sie leer, und die Spalten „Datum“ und „Haltepunkt“ zeigen in jeder Zeile dasselbe. Aylin möchte zuerst alle Gesuche ihrer Richtung sehen und dann eingrenzen, etwa nach Notizen wie „Rad“.

  1. Ändere das GesucheViewModel: Haltepunkt und Datum sind optionale Filter (null heißt „alle“), die Richtung bleibt Pflicht. Eine neue StringProperty notizSuche filtert nach einem Teil der Notiz, ohne Rücksicht auf Groß- und Kleinschreibung und Leerzeichen am Rand. Der Hinweis lautet bei Treffern „n von m offenen Gesuchen“, zuruecksetzen() setzt auch die Suche zurück. Das Binding filterVollstaendig entfällt. (6 Punkte)

  2. Ergänze in der Maske ein Textfeld #notizSuche mit der Beschriftung „Notiz enthält“ (Tastenkürzel Alt+N) und binde es an das ViewModel. (2 Punkte)

  3. Schreibe drei Komponententests für das ViewModel: ohne Haltepunkt und Datum alle Gesuche „zur Schule“ mit passendem Hinweis; die Suche „ RAD “ findet genau Gesuch 1; ein Comparator, der von außen gesetzt wird, sortiert die gefilterte Liste. (5 Punkte)

  4. Erläutere, warum sich die Sortierung in c) ohne Tabelle testen lässt, obwohl in der App die Tabelle den Comparator setzt. (1 Punkt)

  1. ViewModel (Property und Abhängigkeiten 1 Punkt, Prädikat mit optionalen Filtern 3 Punkte, Hinweis 1 Punkt, Zurücksetzen 1 Punkt):
private final StringProperty notizSuche = new SimpleStringProperty(this, "notizSuche", "");

public GesucheViewModel(GesuchQuelle quelle, Executor hintergrund) {
    this.quelle = quelle;
    this.hintergrund = hintergrund;
    gefiltert.predicateProperty().bind(Bindings.createObjectBinding(
            this::filter, haltepunkt, zurSchule, datum, notizSuche));
    hinweis = Bindings.createStringBinding(() -> {
        if (laedt.get()) {
            return "Gesuche werden geladen …";
        }
        if (fehler.get() != null) {
            return "Gesuche nicht geladen: " + fehler.get();
        }
        return passend.isEmpty() ? "Keine offenen Gesuche für diese Auswahl."
                : passend.size() + " von " + gesuche.size() + " offenen Gesuchen";
    }, laedt, fehler, passend, gesuche);
}

/** Ein Filter, der nicht gesetzt ist (null bzw. leer), lässt alle Gesuche durch. */
private Predicate<Gesuch> filter() {
    String richtung = zurSchule.get() ? "zur-schule" : "nach-hause";
    String halt = haltepunkt.get();
    LocalDate tag = datum.get();
    String suche = notizSuche.get() == null ? "" : notizSuche.get().strip().toLowerCase(Locale.GERMANY);
    return g -> g.richtung().equals(richtung)
            && (halt == null || g.haltepunkt().equals(halt))
            && (tag == null || g.datum().equals(tag))
            && g.notiz().toLowerCase(Locale.GERMANY).contains(suche);
}

public void zuruecksetzen() {
    haltepunkt.set(null);
    datum.set(null);
    zurSchule.set(true);
    notizSuche.set("");
}

public StringProperty notizSucheProperty() {
    return notizSuche;
}

Die Werte der Properties werden einmal gelesen, bevor das Prädikat entsteht; so prüft das Lambda bei jedem Gesuch dieselben Werte. Der Controller markiert den Hinweis nur noch bei einem Ladefehler mit der Klasse fehler, weil es keinen „unvollständigen“ Filter mehr gibt.

  1. Maske und Bindung (je 1 Punkt):
<TextField fx:id="notizSuche" promptText="z. B. Rad" prefWidth="220"
           GridPane.columnIndex="1" GridPane.rowIndex="3"/>
<Label text="_Notiz enthält" mnemonicParsing="true" labelFor="$notizSuche"
       GridPane.columnIndex="0" GridPane.rowIndex="3"/>
@FXML private TextField notizSuche;
// in initialize():
notizSuche.textProperty().bindBidirectional(viewModel.notizSucheProperty());

Der Button „Zurücksetzen“ rutscht in die Zeile 4. Bidirektional ist nötig, damit zuruecksetzen() auch das Textfeld leert.

  1. Tests (erster und zweiter je 2 Punkte, dritter 1 Punkt); das ViewModel lädt in @BeforeEach die fünf Beispielgesuche mit Runnable::run als Executor:
private List<Long> ids() {
    return viewModel.getPassendeGesuche().stream().map(Gesuch::id).toList();
}

@Test
void ohneHaltepunktUndDatumZeigtAlleGesucheDerRichtung() {
    assertEquals(List.of(1L, 3L, 5L), ids());
    assertEquals("3 von 5 offenen Gesuchen", viewModel.hinweisProperty().get());
}

@Test
void notizSucheIgnoriertGrossKleinUndLeerzeichen() {
    viewModel.notizSucheProperty().set("  RAD ");
    assertEquals(List.of(1L), ids());
}

@Test
void comparatorDerTabelleSortiertDieGefilterteListe() {
    // so, wie es die Bindung an tabelle.comparatorProperty() nach Klick auf „frühestens“ tut
    viewModel.getPassendeGesuche().setComparator(Comparator.comparing(Gesuch::fruehestens));
    assertEquals(List.of(3L, 1L, 5L), ids());
}
  1. Die Tabelle setzt über die Bindung nur einen Comparator in die SortedList; im Test setzt setComparator(…) denselben Wert direkt. Das ViewModel kennt die Tabelle nicht, deshalb reicht ein normaler JUnit-Test ohne Fenster (1 Punkt).

Aufgabe 3.1: Durchlaufzeiten eines Boards auswerten (Profilkompetenz)

(ca. 16 min Bearbeitungszeit)

Das Team, das die Gesuche-Ansicht baut, führt ein eigenes Board mit den Tickets GES-1 bis GES-8 und zählt die Durchlaufzeit wie im Artikel: Projekttag „Fertig am“ minus Projekttag „Gezogen am“ plus 1. Thomas Becker möchte im nächsten Replenishment wissen, womit er bei neuen Tickets rechnen kann.

Board der Gesuche-Ansicht
Ticket Gezogen am Fertig am Notiz auf dem Board Durchlaufzeit
GES-1 2 4 –
GES-2 3 7 2 Projekttage fertig in „Test“, niemand frei zum Prüfen
GES-3 5 7 –
GES-4 6 13 4 Projekttage blockiert: Endpunkt GET /gesuche fehlte im Backend
GES-5 8 10 –
GES-6 9 12 1 Projekttag fertig in „Test“
GES-7 11 13 –
GES-8 12 15 –
  1. Berechne die Durchlaufzeit jedes Tickets sowie Mittelwert, Median und Spannweite der acht Werte. (6 Punkte)

  2. Beurteile, ob Mittelwert oder Median das typische Ticket dieses Boards besser beschreibt, und begründe mit GES-4. (3 Punkte)

  3. Thomas Becker fragt: „Wenn ihr heute ein neues Standard-Ticket zieht, wann ist es fertig?“ Formuliere eine Antwort auf Grundlage der acht Durchlaufzeiten und erläutere, warum „im Schnitt nach 4,1 Tagen“ keine gute Antwort ist. (3 Punkte)

  4. Bestimme, wie viele der Projekttage in den Durchlaufzeiten auf Warten oder Blockaden entfallen, und leite zwei Regeln oder Maßnahmen für das Board ab, die die Durchlaufzeit verkürzen, ohne dass jemand schneller programmieren muss. (5 Punkte)

  1. Durchlaufzeiten (je zwei richtige Werte 1 Punkt, zusammen 4 Punkte; Mittelwert und Median 1 Punkt, Spannweite 1 Punkt):
Durchlaufzeiten in Projekttagen (Lösung)
Ticket Gezogen am Fertig am Rechnung Durchlaufzeit
GES-1 2 4 4 − 2 + 1 3
GES-2 3 7 7 − 3 + 1 5
GES-3 5 7 7 − 5 + 1 3
GES-4 6 13 13 − 6 + 1 8
GES-5 8 10 10 − 8 + 1 3
GES-6 9 12 12 − 9 + 1 4
GES-7 11 13 13 − 11 + 1 3
GES-8 12 15 15 − 12 + 1 4
  • Mittelwert: (3 + 5 + 3 + 8 + 3 + 4 + 3 + 4) / 8 = 33 / 8 ≈ 4,1 Projekttage
  • Median: sortiert 3, 3, 3, 3, 4, 4, 5, 8; bei acht Werten liegt er zwischen dem 4. und 5. Wert: (3 + 4) / 2 = 3,5 Projekttage
  • Spannweite: 3 bis 8 Projekttage (Differenz 5)
  1. Der Median beschreibt das typische Ticket besser (1 Punkt). GES-4 ist ein Ausreißer: Es war vier Tage blockiert und hebt den Mittelwert allein um etwa einen halben Tag (ohne GES-4: 25 / 7 ≈ 3,6) (1 Punkt). Der Median bleibt mit und ohne GES-4 bei 3 bis 3,5 Tagen, weil er nur von der Lage der mittleren Werte abhängt; sechs von acht Tickets brauchten 3 oder 4 Tage (1 Punkt).

  2. Eine mögliche Antwort (2 Punkte): „Von acht Tickets waren sechs nach höchstens 4 und sieben nach höchstens 5 Projekttagen fertig. Wir rechnen also mit 5 Projekttagen; nur wenn das Ticket an einer Blockade hängt wie GES-4, kann es länger dauern.“ Ein Mittelwert von 4,1 Tagen klingt genau, sagt aber nichts über die Streuung (1 Punkt): Zwei der acht Tickets dauerten länger, eines doppelt so lange, und den Mittelwert selbst hat kein einziges Ticket erreicht. Thomas Becker würde sich auf einen Termin verlassen, der für einen Teil der Tickets nicht hält. Eine Aussage über einen Anteil der Tickets (etwa 7 von 8, knapp 90 %) zeigt, wie sicher die Zusage ist. Mit acht Tickets ist sie noch unsicher und wird mit jedem Ticket besser.

  3. Wartezeit (1 Punkt): 2 + 4 + 1 = 7 der 33 Projekttage, also gut ein Fünftel, entfallen auf Warten und Blockaden; in dieser Zeit arbeitete niemand an den Tickets.

Je Regel oder Maßnahme mit Bezug zu den Daten 2 Punkte, z. B.:

  • Gegen das Warten in „Test“ (GES-2, GES-6): Von rechts nach links schauen und zuerst prüfen, bevor jemand ein neues Ticket zieht; wer frei wird, prüft. Alternativ ein kleineres WIP-Limit für „In Arbeit“, damit Kapazität für „Test“ frei bleibt.
  • Gegen Blockaden durch fehlende Endpunkte (GES-4): Abhängigkeiten zum Backend schon im Replenishment prüfen und einen fehlenden Endpunkt früh als Ticket an das Backend-Team geben (im Projekt als Expedite-Ticket); ein Ticket mit offener Abhängigkeit nicht nach „Bereit“ holen. Bis der Endpunkt da ist, kann gegen WireMock entwickelt werden.
  • Blockierte Tickets im täglichen Kanban-Meeting zuerst besprechen und eine Person benennen, die die Blockade löst.

Hinweis zur Nachnutzung als Open Educational Resource (OER)

Dieser Artikel und seine Texte, Bilder, Grafiken, Code und sonstiger Inhalt sind - sofern nicht anders angegeben - lizenziert unter CC BY 4.0. Nennung gemäß TULLU-Regel bitte wie folgt: “Übungsaufgaben zur TableView: Mitfahrgesuche als Tabelle und Durchlaufzeiten” von oer-informatik.de (H. Stein), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/javafx-tableview-buchungen-uebungsaufgaben veröffentlicht, die Quelltexte sind in weiterverarbeitbarer Form verfügbar im Repository unter https://gitlab.com/oer-informatik/javafx/javafx-einstieg. Erstellt mit KI-Assistenz; von mir umfassend redaktionell überarbeitet, geprüft und freigegeben. Stand: 11.11.2026.

[Kommentare zum Artikel lesen, schreiben] / [Artikel teilen] / [gitlab-Issue zum Artikel schreiben]

Kommentare gerne per Mastodon, Verbesserungsvorschläge per gitlab issue (siehe oben). Beitrag teilen: