Aufgabenblock — AFB II
Zehn mehrschrittige Aufgaben, bunt gemischt aus allen Unterkapiteln: Klassendiagramme aus Texten erstellen, Klassen und Methoden mit Schleifen implementieren, Aufrufe mit Referenzen und Aliasen verfolgen, Reihungen von Objekten auswerten und Fehler im Quelltext finden. Die gestuften Tipps helfen, wenn du nicht weiterkommst.
Eine Stadtbibliothek beschreibt ihre Anforderungen so:
„Jedes Medium hat eine Signatur und einen Titel; beides ändert sich nach dem Erfassen nicht mehr. Man muss abfragen können, ob ein Medium ausgeliehen ist und von wem. Ein Leser hat eine Ausweisnummer und einen Namen. Er darf höchstens fünf Medien gleichzeitig ausleihen. Ein Leser kann ein Medium ausleihen und zurückgeben; außerdem soll man abfragen können, wie viele Medien er gerade hat.“
Erstellen Sie ein Klassendiagramm mit den Klassen Medium und Leser (Attribute, Konstruktoren, Methoden, Assoziation mit Kardinalitäten).
null). „höchstens fünf Medien“ spricht für eine Reihung vom Typ Medium mit Länge 5 und einen Zähler.Vollständige Lösung
- - ausweisNr: Ganzzahl
- - name: Zeichenkette
- - medien: Reihung vom Typ Medium
- - anzahl: Ganzzahl
- c Leser(ausweisNr: Ganzzahl, name: Zeichenkette)
- + ausleihen(m: Medium): Wahrheitswert
- + zurueckgeben(m: Medium)
- + getAnzahl(): Ganzzahl
- - signatur: Zeichenkette
- - titel: Zeichenkette
- - ausleiher: Leser
- c Medium(signatur: Zeichenkette, titel: Zeichenkette)
- + getSignatur(): Zeichenkette
- + getTitel(): Zeichenkette
- + istAusgeliehen(): Wahrheitswert
- + getAusleiher(): Leser
- + setAusleiher(l: Leser)
Begründungen: Signatur und Titel bekommen keinen Setter (nur lesbar). „Ausgeliehen“ braucht kein eigenes Attribut: istAusgeliehen() liefert ausleiher != null — so können die beiden Angaben nie widersprüchlich werden. ausleihen liefert einen Wahrheitswert, weil es scheitern kann (schon fünf Medien oder Medium schon verliehen). Ein Medium ist bei höchstens einem Leser (0..1), ein Leser hat 0 bis 5 Medien.
So könnte Leser implementiert werden:
public class Leser {
private int ausweisNr;
private String name;
private Medium[] medien;
private int anzahl;
public Leser(int ausweisNr, String name) {
this.ausweisNr = ausweisNr;
this.name = name;
medien = new Medium[5];
anzahl = 0;
}
public boolean ausleihen(Medium m) {
if (anzahl < medien.length && !m.istAusgeliehen()) {
medien[anzahl] = m;
anzahl = anzahl + 1;
m.setAusleiher(this);
return true;
}
return false;
}
public void zurueckgeben(Medium m) {
for (int i = 0; i < anzahl; i++) {
if (medien[i] == m) {
medien[i] = medien[anzahl - 1];
medien[anzahl - 1] = null;
anzahl = anzahl - 1;
m.setAusleiher(null);
return;
}
}
}
public int getAnzahl() {
return anzahl;
}
}
Ein Parkhaus rechnet so ab: Die ersten 15 Minuten sind kostenlos. Danach kostet jede angefangene halbe Stunde 150 Cent, höchstens aber 1200 Cent. Zeiten werden in Minuten seit Mitternacht gespeichert (8:40 Uhr = 520).
- - einfahrt: Ganzzahl
- - bezahlt: Wahrheitswert
- c Parkticket(einfahrt: Ganzzahl)
- + getEinfahrt(): Ganzzahl
- + berechneGebuehr(ausfahrt: Ganzzahl): Ganzzahl
- + bezahlen()
- + istBezahlt(): Wahrheitswert
Implementieren Sie die Klasse. Ermitteln Sie außerdem die Gebühr in Cent für ein Ticket mit Einfahrt 8:40 Uhr bei Ausfahrt um 10:15 Uhr bzw. um 16:40 Uhr.
berechneGebuehr zuerst die Parkdauer ausfahrt − einfahrt bestimmen, dann die drei Regeln nacheinander anwenden.dauer / 30 ab. Der Trick (dauer + 29) / 30 rundet auf: 95 Minuten → 124 / 30 = 4 Takte.Vollständige Lösung
public class Parkticket {
private int einfahrt;
private boolean bezahlt;
public Parkticket(int einfahrt) {
this.einfahrt = einfahrt;
bezahlt = false;
}
public int getEinfahrt() {
return einfahrt;
}
public int berechneGebuehr(int ausfahrt) {
int dauer = ausfahrt - einfahrt;
if (dauer <= 15) {
return 0;
}
int takte = (dauer + 29) / 30;
int gebuehr = takte * 150;
if (gebuehr > 1200) {
gebuehr = 1200;
}
return gebuehr;
}
public void bezahlen() {
bezahlt = true;
}
public boolean istBezahlt() {
return bezahlt;
}
}
10:15 Uhr: Dauer 615 − 520 = 95 min → 4 angefangene halbe Stunden → 4 · 150 = 600 Cent.
16:40 Uhr: Dauer 480 min → 16 Takte → 2400 Cent, gedeckelt auf 1200 Cent.
Wer mit dauer / 30 rechnet, erhält fälschlich 3 Takte (450 Cent). Einen Setter für einfahrt gibt es bewusst nicht — die Einfahrtszeit darf nachträglich nicht manipulierbar sein.
In der Mitgliederverwaltung eines Sportvereins senkt ermaessigen(prozent) den Jahresbeitrag (in Euro) mit der Anweisung beitrag = beitrag - beitrag * prozent / 100;
Mitglied a = new Mitglied("Ole", 120);
Mitglied b = new Mitglied("Ole", 120);
Mitglied c = a;
c.ermaessigen(25);
b = c;
b.ermaessigen(10);
int x = a.getBeitrag();
x = x - 1;
boolean gleich = (a == b);
Stellen Sie den Ablauf in einer Tracetabelle dar (Variablen und Objektzustände) und geben Sie die Endwerte an. Wie viele Mitglied-Objekte werden erzeugt, und welchen Wert hat gleich?
a, b, c jeweils, auf welches Objekt sie zeigen.b = c kopiert die Referenz. Danach zeigen alle drei Variablen auf Objekt 1; Objekt 2 ist nicht mehr erreichbar.Vollständige Lösung
Mitglied a = new Mitglied("Ole", 120); // a -> O1 (120)
Mitglied b = new Mitglied("Ole", 120); // b -> O2 (120)
Mitglied c = a; // c -> O1
c.ermaessigen(25); // O1: 90
b = c; // b -> O1, O2 unerreichbar
b.ermaessigen(10); // O1: 81
int x = a.getBeitrag(); // x = 81 (Kopie des Werts)
x = x - 1; // x = 80, O1 bleibt 81
boolean gleich = (a == b); // true: gleiche Referenz
Endwerte: a.getBeitrag() = 81, x = 80, es wurden 2 Objekte erzeugt, gleich ist true. Hätte man a == b direkt nach Zeile 2 geprüft, wäre das Ergebnis false gewesen — trotz gleicher Attributwerte.
Eine Schul-Wetterstation speichert die Messwerte eines Tages. Weil der Sensor manchmal ausfällt, bleiben Plätze leer.
Messwert[] tag = new Messwert[6];
tag[0] = new Messwert(6, 3.5); // (stunde, temperatur)
tag[1] = new Messwert(9, 7.0);
tag[3] = new Messwert(15, 12.5);
tag[4] = new Messwert(18, 9.0);
double summe = 0;
int anzahl = 0;
double max = -100;
int maxStunde = -1;
for (int i = 0; i < tag.length; i++) {
if (tag[i] != null) {
summe = summe + tag[i].getTemperatur();
anzahl = anzahl + 1;
if (tag[i].getTemperatur() > max) {
max = tag[i].getTemperatur();
maxStunde = tag[i].getStunde();
}
}
}
double mittel = summe / anzahl;
Analysieren Sie den Quelltext: Welche Werte haben mittel und maxStunde am Ende? Erläutern Sie, was ohne die Prüfung tag[i] != null passieren würde.
i von 0 bis 5 durch. Bei den Indizes 2 und 5 steht null — diese Durchläufe überspringt die if-Anweisung.anzahl (belegte Plätze), nicht durch tag.length.Vollständige Lösung
Belegte Plätze: 0, 1, 3, 4. summe = 3,5 + 7,0 + 12,5 + 9,0 = 32,0; anzahl = 4 → mittel = 8,0 °C. Die höchste Temperatur 12,5 °C wurde um 15 Uhr gemessen, also maxStunde = 15.
Ohne Null-Prüfung würde bei i = 2 der Aufruf tag[2].getTemperatur() auf null erfolgen → NullPointerException, das Programm bricht ab. Wer durch tag.length teilt, erhält fälschlich 5,33 °C.
Ein Reservierungssystem verwaltet die Wagen eines Zuges. Die Klasse Wagen ist fertig.
- - wagen: Reihung vom Typ Wagen
- - anzahl: Ganzzahl
- c Zug(maxWagen: Ganzzahl)
- + anhaengen(w: Wagen): Wahrheitswert
- + getFreiePlaetze(): Ganzzahl
- + findeWagenMitPlatz(n: Ganzzahl): Wagen
- - nummer: Ganzzahl
- - plaetze: Ganzzahl
- - belegt: Ganzzahl
- c Wagen(nummer: Ganzzahl, plaetze: Ganzzahl, belegt: Ganzzahl)
- + getNummer(): Ganzzahl
- + getPlaetze(): Ganzzahl
- + getFrei(): Ganzzahl
Implementieren Sie die Klasse Zug. anhaengen liefert false, wenn der Zug voll ist; findeWagenMitPlatz(n) liefert den ersten Wagen mit mindestens n freien Plätzen, sonst null.
Geben Sie für einen Zug mit den Wagen 21 (60 Plätze, 58 belegt), 22 (80 Plätze, 71 belegt) und 23 (60 Plätze, 12 belegt) die Rückgabe von getFreiePlaetze() und die Nummer des Wagens an, den findeWagenMitPlatz(5) liefert.
anzahl − 1. Die Schleife läuft deshalb bis anzahl, nicht bis wagen.length — so brauchst du keine Null-Prüfung.return zurückgeben. return null; steht erst nach der Schleife.Vollständige Lösung
public class Zug {
private Wagen[] wagen;
private int anzahl;
public Zug(int maxWagen) {
wagen = new Wagen[maxWagen];
anzahl = 0;
}
public boolean anhaengen(Wagen w) {
if (anzahl < wagen.length) {
wagen[anzahl] = w;
anzahl = anzahl + 1;
return true;
}
return false;
}
public int getFreiePlaetze() {
int summe = 0;
for (int i = 0; i < anzahl; i++) {
summe = summe + wagen[i].getFrei();
}
return summe;
}
public Wagen findeWagenMitPlatz(int n) {
for (int i = 0; i < anzahl; i++) {
if (wagen[i].getFrei() >= n) {
return wagen[i];
}
}
return null;
}
}
Freie Plätze: 2 + 9 + 48 = 59. findeWagenMitPlatz(5): Wagen 21 hat nur 2 freie Plätze, Wagen 22 hat 9 → Rückgabe ist Wagen 22 (nicht der mit den meisten freien Plätzen, sondern der erste passende). findeWagenMitPlatz(50) liefert null.
Typischer Fehler: return null; im else-Zweig innerhalb der Schleife — dann wird nur der erste Wagen geprüft.
Ein Mitschüler hat für die Bestellsoftware einer Pizzeria diese Klasse geschrieben. Sie enthält sechs Fehler — manche verhindern das Kompilieren, andere führen zu falschem Verhalten oder verletzen die Datenkapselung.
public class Bestellung {
public String kunde;
private int anzahlPizzen;
private boolean geliefert;
public void Bestellung(String kunde) {
kunde = kunde;
anzahlPizzen = 0;
}
public void pizzaHinzufuegen(int anzahl) {
if (anzahl > 0) {
anzahlPizzen = anzahlPizzen + anzahl;
}
}
public int getAnzahlPizzen() {
anzahlPizzen;
}
public boolean istFuer(String name) {
return kunde == name;
}
public void liefern() {
geliefert = true;
}
}
// in einer Testklasse:
Bestellung b;
b.pizzaHinzufuegen(2);
Überprüfen Sie den Quelltext: Benennen Sie die sechs Fehler, ordnen Sie sie ein (Syntaxfehler / Laufzeit- oder Logikfehler / Verstoß gegen die Kapselung) und korrigieren Sie sie.
this, return, Vergleich von Zeichenketten, Objekterzeugung.void ist Bestellung(...) eine gewöhnliche Methode. Was bedeutet das für new Bestellung("Ada")?Bestellung ist nur ein Verweis. Worauf zeigt b?Vollständige Lösung
public String kunde;— Kapselung verletzt: Attribute sind privat.public void Bestellung(...)— mitvoidist das kein Konstruktor.new Bestellung("Ada")scheitert beim Kompilieren, weil es keinen passenden Konstruktor gibt.kunde = kunde;— Logikfehler: Der Parameter überdeckt das Attribut, das Attribut bleibtnull. Richtig:this.kunde = kunde;anzahlPizzen;ohnereturn— Syntaxfehler („not a statement“, fehlende Rückgabe).kunde == name— Logikfehler: vergleicht Referenzen, nicht Inhalte. Richtig:kunde.equals(name)Bestellung b;ohnenew— Syntaxfehler (Variable nicht initialisiert); mitBestellung b = null;wäre es eineNullPointerExceptionzur Laufzeit.
Korrigierte Fassung:
public class Bestellung {
private String kunde;
private int anzahlPizzen;
private boolean geliefert;
public Bestellung(String kunde) {
this.kunde = kunde;
anzahlPizzen = 0;
geliefert = false;
}
public void pizzaHinzufuegen(int anzahl) {
if (anzahl > 0) {
anzahlPizzen = anzahlPizzen + anzahl;
}
}
public int getAnzahlPizzen() {
return anzahlPizzen;
}
public boolean istFuer(String name) {
return kunde.equals(name);
}
public void liefern() {
geliefert = true;
}
}
Bestellung b = new Bestellung("Ada");
b.pizzaHinzufuegen(2);
In einer Werkstatt kann ein Auftrag einem Mechaniker zugewiesen sein — oder noch keinem.
- - beschreibung: Zeichenkette
- - kennzeichen: Zeichenkette
- - mechaniker: Mechaniker
- - erledigt: Wahrheitswert
- c Auftrag(beschreibung: Zeichenkette, kennzeichen: Zeichenkette)
- - name: Zeichenkette
- - offeneAuftraege: Ganzzahl
- c Mechaniker(name: Zeichenkette)
- + getName(): Zeichenkette
- + getOffeneAuftraege(): Ganzzahl
- + auftragUebernehmen()
- + auftragAbschliessen()
Erweitern Sie die Klasse Auftrag um:
+ zuweisen(m: Mechaniker): Wahrheitswert— nur möglich, wenn noch kein Mechaniker zugewiesen ist; der Mechaniker übernimmt dann den Auftrag.+ getMechanikerName(): Zeichenkette— liefert „nicht zugewiesen“, wenn es keinen Mechaniker gibt.+ abschliessen()— nur ein zugewiesener, noch nicht erledigter Auftrag kann abgeschlossen werden.
Geben Sie an, welchen Wert m.getOffeneAuftraege() nach diesen Anweisungen liefert:
Mechaniker m = new Mechaniker("Deniz");
Auftrag a1 = new Auftrag("Bremsen wechseln", "H-KW 204");
Auftrag a2 = new Auftrag("Ölwechsel", "H-LT 88");
Auftrag a3 = new Auftrag("Reifen einlagern", "HI-A 512");
a1.zuweisen(m);
a2.zuweisen(m);
a1.abschliessen();
a1.abschliessen();
a3.abschliessen();
mechaniker auf null. Erst danach darfst du Methoden über die Referenz aufrufen.m.auftragUebernehmen() bzw. mechaniker.auftragAbschliessen() auf.Vollständige Lösung
public class Auftrag {
private String beschreibung;
private String kennzeichen;
private Mechaniker mechaniker;
private boolean erledigt;
public Auftrag(String beschreibung, String kennzeichen) {
this.beschreibung = beschreibung;
this.kennzeichen = kennzeichen;
mechaniker = null;
erledigt = false;
}
public boolean zuweisen(Mechaniker m) {
if (mechaniker == null && m != null) {
mechaniker = m;
m.auftragUebernehmen();
return true;
}
return false;
}
public String getMechanikerName() {
if (mechaniker == null) {
return "nicht zugewiesen";
}
return mechaniker.getName();
}
public void abschliessen() {
if (mechaniker != null && !erledigt) {
erledigt = true;
mechaniker.auftragAbschliessen();
}
}
}
Ablauf: Nach zwei Zuweisungen hat Deniz 2 offene Aufträge. Der erste a1.abschliessen() senkt auf 1. Der zweite Aufruf ändert nichts, weil a1 schon erledigt ist. a3.abschliessen() ändert ebenfalls nichts: a3 hat keinen Mechaniker — ohne die Null-Prüfung gäbe es hier eine NullPointerException. Ergebnis: 1.
Ein Konzertveranstalter codiert Tickets als Zeichenkette aus Bereich und Platznummer, getrennt durch einen Bindestrich, z. B. "Innenraum-112" oder "Balkon-7".
- - verkauft: Reihung vom Typ Ticket
- - anzahl: Ganzzahl
- c Konzert(kapazitaet: Ganzzahl)
- + verkaufen(t: Ticket): Wahrheitswert
- + anzahlImBereich(bereich: Zeichenkette): Ganzzahl
- - code: Zeichenkette
- c Ticket(code: Zeichenkette)
- + getBereich(): Zeichenkette
- + getPlatz(): Zeichenkette
a) Ermitteln Sie für code = "Innenraum-112" die Rückgaben von code.length() und code.indexOf("-").
b) Implementieren Sie getBereich() und getPlatz() in Ticket sowie anzahlImBereich(bereich) in Konzert.
substring(0, code.indexOf("-")). Der Platz beginnt ein Zeichen hinter dem Bindestrich und reicht bis code.length().anzahlImBereich läuft eine Schleife bis anzahl und vergleicht mit equals — nicht mit ==.Vollständige Lösung
a) code.length() = 13 (9 + 1 + 3 Zeichen), code.indexOf("-") = 9.
b)
public class Ticket {
private String code;
public Ticket(String code) {
this.code = code;
}
public String getBereich() {
return code.substring(0, code.indexOf("-"));
}
public String getPlatz() {
return code.substring(code.indexOf("-") + 1, code.length());
}
}
public int anzahlImBereich(String bereich) {
int zaehler = 0;
for (int i = 0; i < anzahl; i++) {
if (verkauft[i].getBereich().equals(bereich)) {
zaehler = zaehler + 1;
}
}
return zaehler;
}
Weil die Methode getBereich() das Zerlegen übernimmt, muss Konzert das Format der Codes nicht kennen. Ändert sich das Format, wird nur Ticket angepasst.
Die Software einer Tierarztpraxis speichert zu jedem Tier seinen Halter.
- - name: Zeichenkette
- - gewichtKg: Fließkommazahl
- - halter: Halter
- c Tier(name: Zeichenkette, gewichtKg: Fließkommazahl, halter: Halter)
- + setHalter(halter: Halter)
- + zunehmen(kg: Fließkommazahl)
- + getGewichtKg(): Fließkommazahl
- - name: Zeichenkette
- - telefon: Zeichenkette
- c Halter(name: Zeichenkette, telefon: Zeichenkette)
Halter h1 = new Halter("Frau Yilmaz", "0511 4471");
Tier t1 = new Tier("Bruno", 31.5, h1);
Tier t2 = new Tier("Minka", 4.25, h1);
Halter h2 = new Halter("Herr Brandt", "0511 9020");
t2.setHalter(h2);
t1.zunehmen(0.75);
Tier t3 = t1;
t3.zunehmen(0.5);
Zeichnen Sie das Objektdiagramm nach der letzten Anweisung (alle Objekte mit Attributwerten und Referenzen) und geben Sie das Gewicht von Bruno an.
new-Aufrufe: So viele Objektkarten zeichnest du. t3 ist nur ein weiterer Name.halter enthält kein Halter-Objekt, sondern einen Verweis — zeichne einen Pfeil oder schreibe halter = h1.t1 und t3 verweisen auf dasselbe Tier: 31,5 + 0,75 + 0,5.Vollständige Lösung
- name = "Bruno"
- gewichtKg = 32.75
- halter = h1
- name = "Minka"
- gewichtKg = 4.25
- halter = h2
- name = "Frau Yilmaz"
- telefon = "0511 4471"
- name = "Herr Brandt"
- telefon = "0511 9020"
Es gibt vier Objekte. t3 ist ein Alias von t1 (keine eigene Karte, höchstens ein zweiter Pfeil auf Bruno). Minka verweist nach setHalter(h2) auf Herrn Brandt; Frau Yilmaz wird weiterhin von Bruno referenziert. Bruno wiegt 31,5 + 0,75 + 0,5 = 32,75 kg.
Die Halter-Objekte kennen ihre Tiere nicht — die Assoziation ist hier nur in einer Richtung umgesetzt.
Ein Stromzähler läuft nie rückwärts, und der Tarif muss zwischen 20 und 60 Cent pro kWh liegen. Ungültige Werte werden ignoriert; ablesen liefert dann 0.
public class Stromzaehler {
private int zaehlerstand;
private int tarifCent;
public Stromzaehler(int start) {
zaehlerstand = start;
tarifCent = 30;
}
public void setTarifCent(int tarifCent) {
if (tarifCent >= 20 && tarifCent <= 60) {
this.tarifCent = tarifCent;
}
}
public int ablesen(int neuerStand) {
if (neuerStand < zaehlerstand) {
return 0;
}
int verbrauch = neuerStand - zaehlerstand;
zaehlerstand = neuerStand;
return verbrauch;
}
public int getZaehlerstand() {
return zaehlerstand;
}
public int getTarifCent() {
return tarifCent;
}
}
Stromzaehler z = new Stromzaehler(10250); z.setTarifCent(35); z.setTarifCent(5); int v1 = z.ablesen(10430); int v2 = z.ablesen(10400); int v3 = z.ablesen(10510); int kosten = (v1 + v2 + v3) * z.getTarifCent();
Bestimmen Sie den Zählerstand und den Wert von kosten am Ende. Begründen Sie, warum die Klasse keinen öffentlichen Setter setZaehlerstand anbietet.
zaehlerstand, tarifCent und die Rückgaben v1, v2, v3.Vollständige Lösung
Tarif: 30 → 35 (5 wird abgelehnt). v1 = 10430 − 10250 = 180; v2 = 0 (10400 < 10430, abgelehnt); v3 = 10510 − 10430 = 80. Zählerstand am Ende: 10510 kWh. kosten = (180 + 0 + 80) · 35 = 9100 Cent (91 €).
Begründung: Ein freier Setter würde erlauben, den Zählerstand zurückzudrehen — der Zustand wäre dann nicht mehr gültig, und Verbräuche könnten verschwinden. Stattdessen gibt es die fachliche Methode ablesen, die nur plausible Werte annimmt und zugleich den Verbrauch liefert. Das ist der Kern der Datenkapselung: Das Objekt selbst sorgt dafür, dass sein Zustand gültig bleibt.
- zaehlerstand = 10250
- tarifCent = 30
