Hier sind die 12 häufigsten Fehler, die in Klausuren zur objektorientierten Modellierung vorkommen — von der Klassenkarte über Assoziationen bis zur Vererbung. Jede Karte zeigt links den fehlerhaften und den korrigierten Code oder das Diagramm, rechts die Erklärung. Wer einen Fehler kennt, macht ihn seltener.
Die 12 häufigsten Fehler
1Parameter überdeckt Attribut — this fehlt
// falsch
public Pflanze(String art, int hoehe) {
art = art;
hoehe = hoehe;
}
// richtig
public Pflanze(String art, int hoehe) {
this.art = art;
this.hoehe = hoehe;
}
So wird es oft gemacht: Die Parameter heißen wie die Attribute, im Rumpf steht art = art;. Das übersetzt ohne Fehler — aber art bezeichnet dort beide Male den Parameter. Das Attribut bleibt null, hoehe bleibt 0.
Richtig ist: this.art = art; — links das Attribut des Objekts, rechts der Parameter. Alternativ bekommen die Parameter andere Namen, z. B. neueArt.
Heißen Parameter und Attribut gleich, gehört vor das Attribut ein this..
2Konstruktor mit Rückgabetyp void
// falsch
public void Getraenkeautomat(int flaschen) {
this.flaschen = flaschen;
}
// new Getraenkeautomat(20) → Übersetzungsfehler
// richtig
public Getraenkeautomat(int flaschen) {
this.flaschen = flaschen;
}
So wird es oft gemacht: „Der Konstruktor gibt nichts zurück, also schreibe ich void.“ Java hält void Getraenkeautomat(int flaschen) dann für eine normale Methode. Der Aufruf new Getraenkeautomat(20) findet keinen passenden Konstruktor.
Richtig ist: Der Konstruktor hat überhaupt keinen Rückgabetyp und heißt exakt wie die Klasse. Im Diagramm steht er mit c: c Getraenkeautomat(flaschen: Ganzzahl).
Konstruktor: Name der Klasse, kein Rückgabetyp — auch nicht void.
3Getter ohne return, Setter ohne Prüfung
// falsch public void getSchritte() { System.out.println(schritte); } public void setPuls(int puls) { this.puls = puls; // auch -20 }
// richtig
public int getSchritte() {
return schritte;
}
public void setPuls(int puls) {
if (puls >= 30 && puls <= 220) {
this.puls = puls;
}
}
So wird es oft gemacht: Beim Fitnesstracker gibt getSchritte() den Wert auf dem Bildschirm aus, statt ihn zurückzugeben — int s = f.getSchritte(); übersetzt dann nicht. Der Setter übernimmt jeden Wert, auch einen Puls von −20.
Richtig ist: Der Getter hat den Attributtyp als Rückgabetyp und endet mit return. Der Setter übernimmt nur plausible Werte; sonst bleibt der alte Zustand erhalten.
Getter: return, nicht println. Setter: erst prüfen, dann zuweisen.
4Zeichenketten mit == verglichen
// falsch
public boolean istRichtig(String antwort) {
return antwort == loesung;
}
// richtig
public boolean istRichtig(String antwort) {
return antwort.equals(loesung);
}
So wird es oft gemacht: In der Klasse Frage eines Quiz wird die eingegebene Antwort mit == geprüft. Kommt die Antwort aus einer Eingabe, ist sie ein anderes String-Objekt — das Ergebnis ist false, obwohl „Paris“ und „Paris“ gleich aussehen.
Richtig ist: antwort.equals(loesung) vergleicht die Zeichen. == prüft nur, ob beide Variablen auf dasselbe Objekt zeigen; dass es in Tests manchmal zufällig klappt, macht es nicht richtig.
Zeichenketten und andere Objekte inhaltlich immer mit equals vergleichen.
5Zuweisung für eine Kopie gehalten
// falsch: Kopie für nächste Woche?
Termin kopie = original;
kopie.setTag(14);
// original.getTag() ist jetzt auch 14
// richtig: neues Objekt Termin kopie = new Termin(original.getTitel(), 14);
So wird es oft gemacht: In einer Kalender-App soll ein Termin für die nächste Woche kopiert werden. kopie = original kopiert aber nur die Referenz. Danach zeigen beide Variablen auf denselben Termin, und der ursprüngliche Tag ist verloren.
Richtig ist: Eine echte Kopie ist ein neues Objekt mit new, das die Werte des Originals über Getter übernimmt. Nur bei primitiven Typen wie int kopiert die Zuweisung den Wert.
Zuweisung bei Objekten = zweiter Name für dasselbe Objekt (Alias).
6Kardinalität steht an der falschen Seite
falsch:
richtig:
So wird es oft gemacht: Der Stern wird neben die Klasse geschrieben, die im Satz „viele“ ist — also direkt neben Team. Gelesen heißt das Diagramm dann: Ein Spieler kennt viele Teams, und im Code bekäme das Team nur eine einzelne Referenz.
Richtig ist: Die Kardinalität steht am anderen Ende der Linie, bei der gezählten Klasse: Team ——spieler——> * Spieler. Im Code wird daraus private ArrayList<Spieler> spieler; in Team.
Lies: „Ein Objekt links kennt [Zahl rechts] Objekte rechts.“
7Liste deklariert, aber nie erzeugt
// falsch
private ArrayList<Spieler> spieler;
public Team(String name) {
this.name = name;
}
// team.aufnehmen(s) → NullPointerException// richtig
public Team(String name) {
this.name = name;
spieler = new ArrayList<Spieler>();
}
So wird es oft gemacht: Das Attribut für die *-Beziehung wird richtig deklariert, im Konstruktor aber vergessen. Der Compiler meldet nichts — erst der erste add-Aufruf bricht mit einer NullPointerException ab.
Richtig ist: Jede Liste und jede Reihung braucht ein eigenes new, am besten im Konstruktor. Bei einer Reihung fester Länge zusätzlich beachten: new Spieler[11] legt nur leere Plätze an, keine Spieler.
Deklarieren heißt nicht erzeugen: ohne new keine Liste.
8super(…) fehlt oder steht nicht am Anfang
// falsch
public Girokonto(String inhaber, double dispo) {
this.dispo = dispo;
super(inhaber);
}// richtig
public Girokonto(String inhaber, double dispo) {
super(inhaber);
this.dispo = dispo;
}
So wird es oft gemacht: Im Konstruktor der Unterklasse werden zuerst die eigenen Attribute gesetzt und danach die Oberklasse aufgerufen — oder super fehlt ganz. Beides führt zu einem Übersetzungsfehler, wenn Konto keinen parameterlosen Konstruktor hat.
Richtig ist: super(…) ist die erste Anweisung im Konstruktor der Unterklasse. Die Parameter reicht man einfach durch; die eigenen Attribute folgen danach.
Zuerst die Oberklasse, dann das Eigene.
9Direkter Zugriff auf private Attribute der Oberklasse
// falsch (gehalt ist in Mitarbeiter privat)
@Override
public double getGehalt() {
return gehalt / 2;
}// richtig
@Override
public double getGehalt() {
return super.getGehalt() / 2;
}
So wird es oft gemacht: Weil eine Aushilfe ein Mitarbeiter ist und das Gehalt „erbt“, wird in der Unterklasse direkt mit gehalt gerechnet. Ist das Attribut in der Oberklasse mit - deklariert, meldet der Compiler einen Fehler.
Richtig ist: Das Objekt besitzt das Attribut, aber nur die Oberklasse darf es direkt anfassen. Die Unterklasse nutzt Getter oder super.methode() — oder das Attribut wird bewusst mit # (protected) deklariert.
- bleibt privat, auch für Unterklassen; # öffnet für sie.
10Unterklassen-Methode über eine Oberklassen-Variable
// falsch
Fahrzeug f = new LKW("H-A 1", 12);
f.beladen(3); // Fahrzeug kennt beladen nicht// richtig
LKW l = new LKW("H-A 1", 12);
l.beladen(3);
Fahrzeug f = l; // für Polymorphie weiterhin möglich
So wird es oft gemacht: Weil das Objekt ein LKW ist, wird angenommen, dass auch über f alle LKW-Methoden erreichbar sind. Der Compiler prüft aber den Variablentyp Fahrzeug — und dort gibt es kein beladen.
Richtig ist: Ob ein Aufruf erlaubt ist, entscheidet der Typ der Variable; welcher Rumpf läuft, entscheidet das Objekt. Für spezielle Methoden eine Variable vom Unterklassentyp verwenden.
Erlaubt: nach Variable. Ausgeführt: nach Objekt.
11Vererbung, wo „hat ein“ gemeint ist
falsch:
richtig:
So wird es oft gemacht: Weil ein Auto die Leistung des Motors „mitbenutzen“ soll, wird Auto zur Unterklasse von Motor. Dann erbt das Auto Drehzahl und Hubraum als eigene Attribute, und ein Motortausch ist unmöglich.
Richtig ist: Vererbung nur, wenn „B ist ein A“ stimmt. Ein Auto hat einen Motor → Assoziation Auto ——motor——> 1 Motor; das Auto ruft Methoden seines Motors über die Referenz auf.
Erst den ist-ein-Satz sprechen, dann das Dreieck zeichnen.
12Im Entwurf Namen statt Objekte gespeichert
falsch:
- - filmtitel: Zeichenkette
- - saalnummer: Ganzzahl
richtig:
- - film: Film
- - saal: Saal
So wird es oft gemacht: Wie in einer Datenbanktabelle wird nur der Name oder die Nummer des anderen Objekts gespeichert. Um die Dauer des Films zu erfahren, müsste das Programm alle Filme nach dem Titel durchsuchen — und bei zwei gleichnamigen Filmen weiß es nicht, welcher gemeint ist.
Richtig ist: Eine Beziehung zu einem Objekt ist ein Attribut vom Typ der anderen Klasse: - film: Film. Über film.getDauer() ist alles direkt erreichbar, eindeutig und ohne Suchen.
Objekte kennen Objekte — nicht deren Namen.
