Stand: 2012-07-05

Aufruf virtueller Methoden im Konstruktor

Überschreibbare Methoden sollten nie im Konstruktor aufgerufen werden, weil das zu Fehlverhalten führen kann.

Inhalt

Problem

Manchmal ist es verlockend, virtuelle Methoden im Konstruktor aufzurufen, um eine Basisklasse konfigurierbar zu machen. Das folgende in Java geschriebene Beispiel zeigt eine Klasse mit einer Lücke, die von einer abgeleiteten Klasse geschlossen werden soll:

abstract class Base {
  Base() {
    System.out.println(overridableMethod());
  }
  // eine konkrete Methode ohne "final" wäre genauso schlecht
  abstract String overridableMethod();
}

Aber so einfach wie es aussieht, ist es nicht, denn der Konstruktor der Basisklasse Base wird vor dem Konstruktor der abgeleiteten Klasse aufgerufen. Wenn die Methode overridableMethod() also einen Wert verwendet, der durch den Konstruktor der abgeleiteten Klasse gesetzt wird, kommt es zu einem Fehler!

class Child extends Base {
  private final String name;
  Child(String name) {
    this.name = name;
  }
  @Override String overridableMethod() {
    return name; // beim Zugriff noch nicht initialisiert
  }
}

Wenn mit new Child("Kind") eine Instanz der abgeleiteten Klasse erzeugt wird, passiert Folgendes:

  1. Base-Konstruktor wird aufgerufen.
  2. Base-Konstruktor ruft overridableMethod() auf.
  3. overridableMethod() greift auf das Feld name zu, bevor der Child-Konstruktor Gelegenheit hatte, es zu initialisieren.
  4. Die println-Anweisung im Konstruktor der Basisklasse gibt null statt Kind aus!
  5. Child-Konstruktor wird aufgerufen, was nun aber zu spät ist.

Es spielt übrigens keine Rolle, ob die im Konstruktor aufgerufene Methode abstrakt ist oder schon in der Basisklasse eine Implementierung hat. So lange sie überschreibbar (also nicht final) ist, bleibt das Problem bestehen.

Lösung

Die beste und einfachste Lösung ist, Konstruktorparameter zu verwenden:

abstract class Base {
  Base(String name) {
    System.out.println(name);
  }
}

class Child extends Base {
  Child(String name) {
    super(name);
  }
}

Wie erwartet gibt new Child("Kind") nun Kind aus.

Methoden, die nicht explizit für Vererbung entworfen wurden, sollten mit final gekennzeichnet werden, um versehentliches Überschreiben zu verhindern.

Gute Entwicklungsumgebungen wie NetBeans warnen übrigens vor einem "overridable method call in constructor":

Warnung in NetBeans

Weitere Information