Aufgabenblock — AFB II
Zehn mehrschrittige Aufgaben, bunt gemischt aus allen Unterkapiteln: Klassendiagramme aus Texten erstellen, Klassen mit Assoziationen und Vererbung implementieren, Aufrufe mit Referenzen und überschriebenen Methoden verfolgen und Fehler im Quelltext erklären. 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.
Ein Mobilfunkanbieter modelliert seine Tarife mit Vererbung. Tarif.kosten(minuten) liefert grundgebuehr + minuten * 0.09.
public class Flatrate extends Tarif {
public Flatrate(String name, double grundgebuehr) { super(name, grundgebuehr); }
@Override
public double kosten(int minuten) { return grundgebuehr; }
}
public class Kombi extends Tarif {
private int freiminuten;
public Kombi(String name, double g, int freiminuten) { super(name, g); this.freiminuten = freiminuten; }
@Override
public double kosten(int minuten) {
if (minuten <= freiminuten) { return grundgebuehr; }
return super.kosten(minuten - freiminuten);
}
}ArrayList<Tarif> t = new ArrayList<Tarif>();
t.add(new Tarif("Basis", 5.00));
t.add(new Flatrate("Flat", 19.99));
t.add(new Kombi("Kombi", 9.99, 100));
double s = 0;
for (int i = 0; i < t.size(); i++) {
s = s + t.get(i).kosten(300);
}Analysieren Sie den Ablauf und geben Sie die Werte in Euro an.
kosten-Methode.super.kosten(200) mit der Formel aus Tarif — aber mit der Grundgebühr dieses Objekts.Vollständige Lösung
Basis: 5,00 + 27,00 = 32,00 €. Flat: 19,99 € (überschrieben, unabhängig von den Minuten). Kombi: 300 > 100, also super.kosten(200) = 9,99 + 200 · 0,09 = 27,99 €. Summe 79,98 €. Bei 80 Minuten liegt der Kombi-Tarif innerhalb der Freiminuten: 9,99 €.
Die Schleife kennt nur den Typ Tarif — dass drei verschiedene Methoden laufen, liegt an der dynamischen Bindung.
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 Laufgruppe kennt beliebig viele Läufer (Laufgruppe ——mitglieder——> * Laeufer); jeder Läufer hat seine Zeit für 10 km in Minuten (getMinuten()). Ermitteln Sie mit der Methode unten die Durchschnittszeit.
public double durchschnitt() {
if (mitglieder.size() == 0) {
return 0;
}
int summe = 0;
for (int i = 0; i < mitglieder.size(); i++) {
summe = summe + mitglieder.get(i).getMinuten();
}
return (double) summe / mitglieder.size();
}In der Gruppe sind Ben (52 min), Ela (47 min), Finn (61 min) und Gül (48 min).
(double) würde Java ganzzahlig teilen — 252 / 5 ergäbe 50.Vollständige Lösung
(52 + 47 + 61 + 48) / 4 = 208 / 4 = 52. Mit Ida: 252 / 5 = 50,4. Die Abfrage auf eine leere Liste verhindert eine Division durch null. Ohne die Typumwandlung (double) erhielte man 50 — ein typischer Fehler.
Ein Tierheim-Programm hat die Klasse Haustier mit dem privaten Attribut name, dem Konstruktor Haustier(String name) und den Methoden getName() und steckbrief(), die den Namen liefert. Ein Praktikant schreibt:
public class Katze extends Haustier {
private boolean freigaenger;
public Katze(String name, boolean freigaenger) {
this.freigaenger = freigaenger;
}
@Override
public String steckbrief() {
return name + (freigaenger ? ", Freigänger" : ", Wohnungskatze");
}
}Erklären Sie, warum sich die Klasse nicht übersetzen lässt, und geben Sie eine korrigierte Fassung an.
name? Und welchen Konstruktor der Oberklasse gibt es?Katze auf ein privates Attribut von Haustier direkt zugreifen?Vollständige Lösung
Fehler 1: Der Konstruktor ruft super(name) nicht auf. Java setzt dann automatisch super() ein — Haustier hat aber keinen parameterlosen Konstruktor. Außerdem würde der Name nie gespeichert. Fehler 2: name ist in Haustier privat; die Unterklasse darf nicht direkt darauf zugreifen.
public class Katze extends Haustier {
private boolean freigaenger;
public Katze(String name, boolean freigaenger) {
super(name);
this.freigaenger = freigaenger;
}
@Override
public String steckbrief() {
if (freigaenger) {
return super.steckbrief() + ", Freigänger";
}
return super.steckbrief() + ", Wohnungskatze";
}
}new Katze("Mia", true).steckbrief() liefert "Mia, Freigänger". Statt super.steckbrief() ginge auch getName().
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);
„Ein Parkhaus hat genau drei Ebenen. Jede Ebene hat höchstens 40 Stellplätze. Ein Stellplatz ist frei oder von genau einem Fahrzeug belegt. Außerdem kennt das Parkhaus beliebig viele Dauerparker.“ Erläutern Sie für jede der vier Beziehungen, welche Kardinalität sie hat und wie sie in Java umgesetzt wird.
null, beliebig viele → Liste.Vollständige Lösung
Parkhaus ——ebenen——> 3 Ebene: feste Anzahl →private Ebene[] ebenen = new Ebene[3];, im Konstruktor alle drei Ebenen erzeugen.Ebene ——plaetze——> 0..40 Stellplatz: Obergrenze →private Stellplatz[] plaetze = new Stellplatz[40];.Stellplatz ——fahrzeug——> 0..1 Fahrzeug:private Fahrzeug fahrzeug;—nullbedeutet frei.Parkhaus ——dauerparker——> * Kunde:private ArrayList<Kunde> dauerparker;, im Konstruktor erzeugt.
Höchstens 3 · 40 = 120 Stellplatz-Objekte.
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 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
