MINT lernen

Typische Fehler — was oft schiefgeht

Zwölf Fehler, die in Klausuren zur Objektorientierung immer wieder Punkte kosten — mit Korrektur und Merksatz, von this bis Polymorphie.

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;
}
Ohne this weist sich der Parameter selbst zu.

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;
}
Mit void wird aus dem Konstruktor eine gewöhnliche Methode.

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;
    }
}
Ein Getter liefert den Wert, ein Setter bewacht ihn.

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);
}
== vergleicht Referenzen, equals den Inhalt.

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);
Zwei Namen, ein Objekt: Die Änderung trifft beide.

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:

Team
spieler*
Spieler

richtig:

Team
spieler*
Spieler
„Ein Team hat viele Spieler“ — gezählt werden die Spieler.

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>();
}
Die Deklaration legt nur eine leere Referenz an.

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;
}
Erst baut die Oberklasse ihren Teil des Objekts, dann die Unterklasse.

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;
}
Geerbt heißt nicht sichtbar.

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
Der Compiler sieht nur den Typ der Variable.

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:

Motor
Auto

richtig:

Auto
motor1
Motor
„Ein Auto ist ein Motor“ — der Satz klingt falsch, und das Modell ist es auch.

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:

Vorstellung
  • - filmtitel: Zeichenkette
  • - saalnummer: Ganzzahl

richtig:

Vorstellung
  • - film: Film
  • - saal: Saal
film1
Film
Ein Titel ist ein Wert, ein Film ist ein Objekt mit Dauer, FSK, …

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.