W3docs

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 constructor

Die 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();    // works

So 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 exists

Wenn 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 4

Kopierkonstruktoren

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 values

Da 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); }   // ERROR

Der 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:

  1. Feld-Standardwerte werden zugewiesen (z. B. werden int-Felder zu 0).
  2. Inline-Feldinitialisierer (int count = 5;) und alle Instanz-Initialisierungsblöcke werden in der Reihenfolge ihres Auftretens ausgeführt.
  3. 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

java— editable, runs on the server

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.

Übungen

Übung
Eine Klasse deklariert nur einen Konstruktor: public Point(int x, int y). Welcher Aufruf schlägt fehl?
Eine Klasse deklariert nur einen Konstruktor: public Point(int x, int y). Welcher Aufruf schlägt fehl?
Was this page helpful?