statements before super(...)statements before super(...)
HappyCoders Glasses

Statements before super(…): Java-Code im Konstruktor

Sven Woltmann
Sven Woltmann
Aktualisiert: 11. Januar 2024

In diesem Artikel erfährst du:

  • wie du ab Java 22 Code in Konstruktoren auch vor dem Aufruf von super(...) oder this(...) ausführen kannst,
  • welche Einschränkungen es dabei gibt,
  • was Prolog und Epilog eines Konstruktors sind,
  • und ob die Neuerungen auch für Records und Enums gelten.

Gehen wir einen Schritt zurück: Warum sollte man Code vor super(...) oder this(...) aufrufen wollen?

Code in Konstruktoren – Status Quo vor Java 22

Die folgenden zwei Beispiele zeigen Workarounds, die bisher erforderlich waren, um vor dem Aufruf von super() oder this() Parameter zu validieren oder zu berechnen.

Use Case 1: Validierung von Parametern

Ein häufiger Use Case ist die Validierung von Parametern einer Kindklasse. Im folgenden Beispiel ruft der Konstruktur von Rectangle erst den Konstruktor der Elternklasse, Shape, auf und validiert und setzt danach die Breite und Höhe:

public class Shape {
  private final Color color;

  public Shape(Color color) {
    this.color = color;
  }
}

public class Rectangle extends Shape {
  private final double width;
  private final double height;

  public Rectangle(Color color, double width, double height) {
    super(color);
    if (width < 0 || height < 0) throw new IllegalArgumentException();
    this.width = width;
    this.height = height;
  }
}Code-Sprache: Java (java)

Effizienter wäre es allerdings, die Parameter zu validieren, bevor der Super-Konstruktor aufgerufen wird. Doch das ist bisher nur mit dem folgenden, extrem unschönen Workaround möglich:

public Rectangle(Color color, int width, int height) {
  super(validateParams(color, width, height));
  this.width = width;
  this.height = height;
}

private static Color validateParams(Color color, int width, int height) {
  if (width < 0 || height < 0) throw new IllegalArgumentException();
  return color;
}

Code-Sprache: Java (java)

Use Case 2: Berechnung eines Arguments, das an mehrere Parameter übergeben wird

Ein weiterer Use Case ist die Berechnung von Werten, die an mehr als einen Superklassen-Konstruktorparameter weitergegeben werden sollen. Im folgenden Beispiel wollen wir ein Quadrat mit vorgegebener Fläche erzeugen (dass eine statische Factory-Methode mit aussagekräftigem Namen dafür geeigneter wäre als der Konstruktor wollen wir an dieser Stelle ignorieren):

public class Square extends Rectangle {
  public Square(Color color, int area) {
    super(color, Math.sqrt(area), Math.sqrt(area));
  }
}Code-Sprache: Java (java)

Um die Wurzel der Fläche nicht zweimal zu berechnen, müssten wir einen Hilfskonstruktor einführen:

public class Square extends Rectangle {
  public Square(Color color, int area) {
    this(color, Math.sqrt(area));
  }

  private Square(Color color, double sideLength) {
    super(color, sideLength, sideLength);
  }
}Code-Sprache: Java (java)

Das ist hier aber auch nur deshalb möglich, weil area vom Typ int ist. Wäre area wie sideLength vom Typ double, würde das nicht funktionieren, da wir dann zwei Konstruktoren mit identischer Signatur hätten.

Und wollten wir zuvor sichergehen, dass area nicht negativ ist, müssten wir eine dritte Methode einführen, da wir auch vor this(...) keinen anderen Code ausführen dürfen:

public class Square extends Rectangle {
  public Square(Color color, int area) {
    this(color, Math.sqrt(validateArea(area)));
  }

  private static double validateArea(int area) {
    if (area < 0) throw new IllegalArgumentException();
    return area;
  }

  private Square(Color color, double sideLength) {
    super(color, sideLength, sideLength);
  }
}
Code-Sprache: Java (java)

Es ist kaum noch ersichtlich, was dieser Code tut.

Java-Code vor super(...) und this(...)

Mit JDK Enhancement Proposal 447 wird in Java 22 – zunächst als Preview-Feature – die Möglichkeit eingeführt, Code auch vor dem Aufruf von super(...) oder this(...) aufzurufen.

Wir können damit zunächst die Validierung der Fläche vor den Aufruf von this(...) ziehen:

public class Square extends Rectangle {
  public Square(Color color, int area) {
    if (area < 0) throw new IllegalArgumentException();
    this(color, Math.sqrt(area));
  }

  private Square(Color color, double sideLength) {
    super(color, sideLength, sideLength);
  }
}Code-Sprache: Java (java)

Und auch den Hilfs-Konstruktor brauchen wir nicht mehr. Parametervalidierung und Berechnung der Seitenlänge können nun direkt im Konstruktor untergebracht werden:

public Square(Color color, int area) {
  if (area < 0) throw new IllegalArgumentException();
  double sideLength = Math.sqrt(area);
  super(color, sideLength, sideLength);
}
Code-Sprache: Java (java)

Bei diesem Konstruktor ist auf einen Blick erkennbar, was der Code macht.

Konstruktor-Prolog und -Epilog

Der Block vor dem Aufruf von super(...) oder this(...) wird Prolog genannt.

Code nach dem Aufruf von super(...) oder this(...) oder Code in einem Konstruktor ohne Aufruf von super(...) oder this(...) wird als Epilog bezeichnet.

Einschränkungen

Im Prolog darf der Code nicht lesend auf Felder der Klasse zugreifen und keine nicht-statische Methoden der Klasse aufrufen. Er darf außerdem keine Instanzen von nicht-statischen inneren Klassen erzeugen, da diese dann eine Referenz auf das potentiell uninitialisierte Elternobjekt haben würden.

Der Prolog des Konstruktors einer inneren Klasse darf hingegen auf Felder und Methoden der äußeren Klasse zugreifen.

Records und Enums

Records und Enums können zwar keine Elternklasse haben, deren Konstruktoren können allerdings mit this(...) alternative Konstruktoren aufrufen.

Auch davor darf nun Code, der den oben genannten Einschränkungen standhält, ausgeführt werden.

Fazit

Der Aufruf von Code vor super(...) oder this(...) erlaubt es, Parameter zu validieren oder zu berechnen, bevor der Super-Konstruktor oder ein alternativer Konstruktor aufgerufen wird. Das ermöglicht deutlich ausdrucksstärkeren Code als die Workarounds, die wir bisher für solche Zwecke konstruieren mussten.

Musstest du auch schon komplizierte Workarounds implementieren, und wie findest du das neue Feature? Lass es mich über die Kommentarfunktion wissen!

Du willst über alle neue Java-Features auf dem Laufenden sein? Dann klicke hier, um dich für den HappyCoders-Newsletter anzumelden.