Java-Konstruktoren
Konstruktoren in Java verwenden — Standard-, parametrisierte und überladene Konstruktoren sowie Konstruktorverkettung.
Ein Konstruktor ist eine spezielle Methode, die einmalig beim Erstellen eines Objekts ausgeführt wird. Seine Aufgabe ist es, die neue Instanz von „alle Felder auf ihren Standardwerten" in den Zustand „einsatzbereit" zu überführen. Bisher haben Sie Felder nach new Dog() einzeln gesetzt; ein Konstruktor erlaubt es, diese Arbeit in den new-Aufruf zu bündeln.
Aufbau eines Konstruktors
Ein Konstruktor sieht wie eine Methode aus, weist aber drei Unterschiede auf: Er trägt denselben Namen wie die Klasse, hat keinen Rückgabetyp (nicht einmal void) und wird nur durch new aufgerufen:
public class Point {
int x, y;
public Point(int x, int y) { // constructor
this.x = x;
this.y = y;
}
}
Point p = new Point(3, 4); // runs the constructorDie Argumentliste steht in den () nach dem Klassennamen im new-Ausdruck. Java ordnet diese Argumentliste einem Konstruktor der Klasse zu – genau wie es einen Methodenaufruf einer Methode zuordnet.
Der Standardkonstruktor
Wenn Sie keinen Konstruktor deklarieren, stellt Java der Klasse einen Standardkonstruktor zur Verfügung – einen No-Arg-Konstruktor, der nichts tut:
public class Empty { } // compiler generates a no-arg constructor
Empty e = new Empty(); // worksSo hat new Dog() in den früheren Beispielen funktioniert, obwohl wir public Dog() { ... } nie geschrieben haben. Sobald Sie irgendeinen eigenen Konstruktor deklarieren, verschwindet der kostenlose Standardkonstruktor:
public class Point {
int x, y;
public Point(int x, int y) { this.x = x; this.y = y; }
}
Point p = new Point(); // ERROR — no no-arg constructor existsWenn Sie beides benötigen, deklarieren Sie beides – siehe Überladen weiter unten.
Warum einen Konstruktor verwenden?
Zwei Gründe.
1. Pflichtfelder. Ein Konstruktor erlaubt es, bestimmte Felder als nicht optional zu kennzeichnen. Wenn man öffentliche Felder nachträglich zuweist, kann ein Aufrufer ein Person-Objekt ohne name erstellen. Mit einem Person(String name)-Konstruktor ist das nicht möglich.
2. Invarianten. Ein Konstruktor kann seine Argumente prüfen, bevor das Objekt in einer nutzbaren Form existiert. Wenn ein Circle einen positiven Radius erfordert, kann der Konstruktor bei einem negativen Wert eine Ausnahme werfen – das fehlerhafte Objekt wird gar nicht erst erstellt.
public class Circle {
double radius;
public Circle(double radius) {
if (radius <= 0) throw new IllegalArgumentException("radius must be > 0");
this.radius = radius;
}
}Der Aufrufer kann nun keinen Circle mit einem nicht-positiven Radius erhalten.
Überladene Konstruktoren
Eine Klasse kann mehrere Konstruktoren mit unterschiedlichen Parameterlisten haben – genau wie überladene Methoden:
public class Rectangle {
double width, height;
public Rectangle() { this(1, 1); }
public Rectangle(double side) { this(side, side); } // a square
public Rectangle(double w, double h) { this.width = w; this.height = h; }
}Jetzt lassen sich alle drei Varianten kompilieren:
Rectangle a = new Rectangle(); // 1 x 1
Rectangle b = new Rectangle(5); // 5 x 5
Rectangle c = new Rectangle(3, 4); // 3 x 4Kopierkonstruktoren
Eine häufige Überladung ist ein Kopierkonstruktor – einer, der eine andere Instanz derselben Klasse entgegennimmt und eine unabhängige Kopie davon erstellt. Java verfügt über keinen eingebauten Kopierkonstruktor (anders als C++), daher schreiben Sie bei Bedarf Ihren eigenen:
public class Point {
int x, y;
public Point(int x, int y) { this.x = x; this.y = y; }
public Point(Point other) { this(other.x, other.y); } // copy constructor
}
Point a = new Point(3, 4);
Point b = new Point(a); // a separate Point with the same valuesDa b ein neues Objekt ist, hat eine spätere Änderung von b.x keinen Einfluss auf a. Bei Klassen, deren Felder selbst veränderliche Objekte sind, sollten auch diese Felder kopiert werden (eine tiefe Kopie), wenn die Kopie vollständig unabhängig sein soll.
this(...) — Konstruktorverkettung
Innerhalb eines Konstruktors ruft this(args) einen anderen Konstruktor derselben Klasse auf. So hat das Rectangle-Beispiel oben die Duplizierung von Feldzuweisungen vermieden: Die beiden Hilfskonstruktoren delegieren an den vollständigen Konstruktor.
Zwei Regeln:
this(...)muss die erste Anweisung im Konstruktorrumpf sein.- Ein Konstruktor kann zu einem anderen Konstruktor verketten.
public Rectangle() { this(1, 1); } // ok
public Rectangle(double s) { System.out.println("hi"); this(s, s); } // ERRORDer Grund für die Regel „erste Anweisung" ist, dass die JVM das Objekt genau einmal vollständig initialisieren muss, bevor anderer Code ausgeführt wird.
super(...) — den Eltern-Konstruktor aufrufen
Wenn eine Klasse eine andere erweitert, ruft jeder Konstruktor entweder explizit einen Eltern-Konstruktor mit super(args) auf oder ruft implizit den No-Arg-Konstruktor des Elternteils auf. Wir werden das vollständig in den Kapiteln zur Vererbung und zum super-Schlüsselwort behandeln; vorerst gilt es nur zu wissen, dass super(...) existiert und dieselbe Regel für die erste Anweisung wie this(...) hat.
Konstruktoren können keinen Wert zurückgeben
Ein Konstruktor hat keinen Rückgabetyp – nicht einmal void. Wenn Sie einen schreiben:
public void Point(int x, int y) { ... } // not a constructor!…ist es eine normale Methode, die zufällig Point heißt. Der Compiler warnt nicht; new Point(3, 4) schlägt dann beim Kompilieren fehl, weil der eigentliche gesuchte Konstruktor nicht existiert. Dies ist ein überraschend häufiger Tippfehler.
Initialisierungsreihenfolge
Für eine einzelne Klasse gilt folgende Reihenfolge:
- Feld-Standardwerte werden zugewiesen (z. B. werden
int-Felder zu0). - Inline-Feldinitialisierer (
int count = 5;) und alle Instanz-Initialisierungsblöcke werden in der Reihenfolge ihres Auftretens ausgeführt. - Der Konstruktorrumpf wird ausgeführt.
public class Demo {
int a = compute("a-init", 1);
int b;
{ b = compute("b-block", 2); } // instance initializer
public Demo() {
System.out.println("constructor; a=" + a + ", b=" + b);
}
static int compute(String label, int v) {
System.out.println(label);
return v;
}
}new Demo() gibt a-init, b-block und dann constructor; a=1, b=2 aus.
In der Praxis gehört fast alle Initialisierung in den Konstruktorrumpf. Instanz-Initialisierungsblöcke sind in echtem Code selten; sie existieren hauptsächlich für Situationen wie anonyme Klassen (die später behandelt werden).
Ein ausgearbeitetes Beispiel
Wie geht es weiter?
Innerhalb von Konstruktoren haben Sie bereits this.field = field gesehen, um einen Parameter von einem Feld zu unterscheiden. Das Kapitel zum this-Schlüsselwort klärt, was this ist und an welchen wenigen Stellen Sie es explizit schreiben müssen.