Das Java super-Schlüsselwort
Mit super auf die Elternklasse zugreifen — Elternkonstruktoren und überschriebene Methoden aufrufen.
super ist das Gegenstück zu this für die Elternklasse. Während this das „aktuelle Objekt" bezeichnet, ist super das „aktuelle Objekt, betrachtet als seine Oberklasse." Es hat nur innerhalb einer Klasse eine Bedeutung, die eine andere erweitert, und erscheint an drei Stellen:
super(args)innerhalb eines Konstruktors — ruft einen Elternkonstruktor auf.super.method(args)innerhalb einer Instanzmethode — ruft die Version der Elternklasse einer überschriebenen Methode auf.super.fieldinnerhalb einer Instanzmethode — liest ein Elternfeld, das die Unterklasse verdeckt.
Das Erste ist bei weitem das Häufigste.
super(...) — einen Elternkonstruktor aufrufen
Jeder Konstruktor einer Unterklasse muss damit beginnen, einen Elternkonstruktor aufzurufen. Wenn Sie keinen angeben, fügt Java super(); als erste Zeile ein — einen Aufruf des parameterlosem Konstruktors der Elternklasse:
public class Animal {
String name;
public Animal() { name = "(unnamed)"; } // no-arg
}
public class Cat extends Animal {
public Cat() {
// super(); ← inserted by the compiler
}
}Wenn die Elternklasse keinen parameterlosem Konstruktor hat, müssen Sie einen explizit aufrufen:
public class Animal {
String name;
public Animal(String name) { this.name = name; } // no no-arg constructor
}
public class Cat extends Animal {
public Cat(String name) {
super(name); // required — there is no Animal()
}
}Der häufigste Fehler bei Vererbung ist, dies zu vergessen. Wenn eine Elternklasse keinen parameterlosem Konstruktor hat, versucht der Compiler dennoch, super(); in jeden Konstruktor einer Unterklasse einzufügen, der super(...) nicht explizit aufruft — und schlägt dann mit "there is no default constructor available in Animal" fehl. Die Lösung besteht darin, einen expliziten super(...)-Aufruf hinzuzufügen, der einem echten Elternkonstruktor entspricht.
Regeln für super(...):
- Es muss die erste Anweisung im Konstruktorrumpf sein.
- Ein Konstruktor kann entweder mit
this(...)zu einem anderen Konstruktor derselben Klasse weiterleiten oder mitsuper(...)einen Elternkonstruktor aufrufen — aber nicht beides. - Die Argumente müssen einem vorhandenen Elternkonstruktor entsprechen.
Warum gilt die Regel „erste Anweisung"?
Die JVM muss den Elternteil des Objekts vollständig konstruieren, bevor Unterklassencode ausgeführt werden kann. Dazu gehört die Initialisierung von final-Feldern, die die Elternklasse deklariert. Würde der Unterklassencode zuerst ausgeführt, würde das bedeuten, diese Felder zu lesen, bevor ihnen ihre Werte zugewiesen wurden.
super.method(args) — die Elternversion aufrufen
Innerhalb einer Instanzmethode ruft super.method(...) die Version der Methode der Elternklasse auf, auch wenn die aktuelle Klasse diese überschrieben hat:
public class Animal {
String speak() { return "(some noise)"; }
}
public class Cat extends Animal {
@Override
String speak() {
return super.speak() + " — actually, meow";
// ^^^^^^^^^^^^^ calls Animal.speak(), not Cat.speak()
}
}
new Cat().speak(); // "(some noise) — actually, meow"Ohne super. würde ein einfaches speak() innerhalb von Cat.speak sich selbst aufrufen und endlos rekursieren.
Das klassische Muster besteht darin, das Verhalten der Elternklasse zu erweitern statt zu ersetzen:
@Override
void onSave() {
super.onSave(); // run parent's save logic first
// then add subclass-specific behavior
}super.method(...) kann nicht zwei Ebenen nach oben reichen — es gibt kein super.super.method() in Java. Wenn Ihre Klasse eine Klasse erweitert, die eine andere erweitert, können Sie die Methode des unmittelbaren Elternteils aufrufen, nicht die des Großelternteils.
super.field — ein verdecktes Feld erreichen
Wenn eine Unterklasse ein Feld mit demselben Namen wie das Feld einer Elternklasse deklariert, verdeckt das Feld der Unterklasse das der Elternklasse:
public class A {
String label = "A's label";
}
public class B extends A {
String label = "B's label";
void show() {
System.out.println(label); // B's label
System.out.println(super.label); // A's label
System.out.println(this.label); // B's label
}
}Dies ist fast immer ein schlechtes Design — zwei Felder mit demselben Namen in einem Eltern-Kind-Paar zu haben ist verwirrend — aber die Sprache stellt super.field zur Verfügung, um Mehrdeutigkeiten zu beseitigen, wenn es vorkommt. Die sauberere Lösung ist, eines der Felder umzubenennen.
Beachten Sie, dass Felder nicht polymorph sind. Im Gegensatz zu überschriebenen Methoden wird der Feldzugriff zur Kompilierzeit basierend auf dem deklarierten Typ der Referenz entschieden. Das Kapitel über Polymorphismus erklärt, warum.
super und static
super existiert wie this nur in Instanzkontexten. Sie können super nicht in einer static-Methode oder einem Initialisierer schreiben — es gibt kein aktuelles Objekt und damit auch keine Elternansicht davon. Um eine auf der Elternklasse deklarierte statische Methode aufzurufen, qualifizieren Sie sie mit dem Klassennamen der Elternklasse:
public class A { static void hi() { System.out.println("hi"); } }
public class B extends A {
static void demo() {
A.hi(); // ok — call by class name
// super.hi(); // ERROR — super in a static context
}
}Eine Unterklasse kann auch eine static-Methode mit demselben Namen wie eine static-Methode der Elternklasse deklarieren — dies wird als Method Hiding bezeichnet, nicht als Überschreiben, und folgt Regeln zur Kompilierzeit. Dies wird kurz im Kapitel über Methodenüberschreibung behandelt.
Ketten von super
Jeder super(...)-Aufruf aktiviert die nächste Ebene der Vererbungskette. Nachverfolgung dessen, was für eine tief verschachtelte Unterklasse ausgeführt wird:
new SiameseCat("Lulu")
→ SiameseCat(String)
→ super("Lulu") — Cat(String)
→ super("Lulu") — Animal(String)
→ super() — Object()
→ Animal body runs
→ Cat body runs
→ SiameseCat body runsDie Initialisierung verläuft immer vom entferntesten Vorfahren bis zur aktuellen Klasse. Wenn der Rumpf von SiameseCat ausgeführt wird, sind alle Felder der Elternklassen vollständig initialisiert.
Ein ausgearbeitetes Beispiel
Was kommt als Nächstes
super ist die Brücke zur Elternklasse, aber die größere Idee, die es ermöglicht, ist Polymorphismus — die Fähigkeit, eine Methode auf einer Referenz mit Elterntyp aufzurufen und sie zur Laufzeit auf die richtige Implementierung der Unterklasse umzuleiten. Das ist das nächste Kapitel. Weiter zu Java-Polymorphismus.