Erstmals eingeführt mit Java 22, dann mit Java 23 und Java 24 fortgeführt, werden Flexible Constructor Bodies mit Java 25 nun finalisiert und offiziell ausgeliefert. Sie helfen, unnötig komplexen Code zu vermeiden und den Code lesbarer und verständlicher zu gestalten.
TL;DR
In Konstruktoren dürfen Anweisungen nun auch vor dem Aufruf des Konstruktors der Oberklasse mittels super()
oder eines anderen Konstruktors derselben Klasse mittels this()
stehen. Das ermöglicht eine bessere Strukturierung des Codes und eine frühere Validierung von Parametern.
Problem
Schauen wir uns ein einfaches Bespiel an. Eine Basisklasse Vehicle
hat einen name
und einen price
. Der Name sollte weder null noch leer sein, und der Preis eine positive Zahl.
public abstract class Vehicle {
String name;
BigDecimal price;
public Vehicle(String name, BigDecimal price) {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("name must not be null or blank");
}
if (price == null) {
throw new IllegalArgumentException("price must not be null");
}
if (price.signum() < 0) {
throw new IllegalArgumentException("price must be >= 0");
}
this.name = name;
this.price = price;
}
...
}
Die Konstruktorparameter werden überprüft, bei Nichtgefallen gibt es eine Exception. So weit, so gut.
Eine Klasse Car
erweitert Vehicle
und hat zusätzlich eine numberOfSeats
und eine numberOfDoors
. Auch diese müssen natürlich bei der Objekterzeugung validiert werden, wobei name
und price
bequem an den Oberklassenkonstruktor durchgereicht werden können:
public CarJdk21(String name, BigDecimal price,
int numberOfSeats, int numberOfDoors) {
// zuerst Aufruf des Oberklassenkonstruktors
super(name, price);
// erst danach Validierung der Parameter
if (numberOfSeats < 1) {
throw new IllegalArgumentException("numberOfSeats must be >= 1");
}
if (numberOfDoors < 1) {
throw new IllegalArgumentException("numberOfDoors must be >= 1");
}
// Zuweisung an die Instanzvariablen
this.numberOfSeats = numberOfSeats;
this.numberOfDoors = numberOfDoors;
}
Nach sorgfältiger Betrachtung des Codes fällt auf: Noch vor der Validierung von numberOfSeats
und numberOfDoors
wird der Konstruktor von Vehicle
aufgerufen, führt diverse Funktionalitäten aus, und belegt am Ende (wenn bis dahin alles geklappt hat) die Attribute name
und price
mit ihren Werten, obwohl noch gar nicht klar ist, ob die weiteren Attribute überhaupt gültig sind.
Leider geht das bisher gar nicht anders, denn in der Java Language Specification (JLS) steht in § 8.8.7. Constructor Body
:
Die erste Anweisung eines Konstruktors kann ein expliziter Aufruf eines anderen Konstruktors derselben Klasse oder der direkten Superklasse sein.
Ein Konstruktor kann beliebigen Code enthalten, ein Aufruf von this()
und super()
ist aber nur in der ersten Zeile erlaubt.
Auftritt: „Flexible Constructor Bodies“.
Wenn ein Konstruktor einen expliziten Konstruktoraufruf enthält, werden die Statements vor dem expliziten Konstruktoraufruf als Prolog des Konstruktorkörpers bezeichnet.
Oder einfacher formuliert: Vor dem Aufruf von this()
oder super()
dürfen jetzt Anweisungen stehen.
In diesem Prolog gelten besondere Regeln.
Erlaubt sind:
- Aufruf von statischen Methoden,
- Zugriff auf Klassenvariablen
Unzulässig sind:
- Referenzen auf das aktuelle Objekt mit
this
odersuper
, - Zugriff auf Instanzvariablen oder Instanzmethoden
Folgendes führt also zu Compilerfehlern:
class Pet {
String name;
Pet(String name) { this.name = name; }
}
class Feeder {
static List<Pet> pets;
static void register(Pet pet) { this.pets.add(pet);}
}
class ColouredPet {
// Instanzvariable
String colour;
Pet(String name) {
// vor dem Aufruf des Konstruktors der Oberklasse
this.colour = "blue"; // <=== Fehler: Attributzugriff
String cuteName = cuteify(cuteName); // <=== Fehler: Methodenzugriff
Feeder.register(this); // <=== Fehler: Refererenz auf this
// Konstruktor der Oberklasse
super(name);
}
// Instanzmethode
String cuteify(String name) {
return name + "y";
}
}
Anwendung
Nach diesen Betrachtungen wenden wir uns unserem einführenden Beispiel zu.
Unsere Klasse Car
können wir nun so umformulieren:
public CarJdk25(String name, BigDecimal price,
int numberOfSeats, int numberOfDoors) {
// Validierung
if (numberOfSeats < 1) {
throw new IllegalArgumentException("numberOfSeats must be >= 1");
}
if (numberOfDoors < 1) {
throw new IllegalArgumentException("numberOfDoors must be >= 1");
}
// Aufruf des Oberklassenkonstruktors
super(name, price);
// initialisierung der Instanzvariablen
this.numberOfSeats = numberOfSeats;
this.numberOfDoors = numberOfDoors;
}
Der Vorteil ist deutlich zu sehen:
- Der alte Weg ():
CarJdk21
Der Aufruf vonsuper(name, price)
muss die erste Anweisung sein. Die Validierung der zusätzlichen Felder (numberOfSeats
,numberOfDoors
) kann erst danach erfolgen. Schlägt diese Validierung fehl, wurde dasVehicle
-Objekt bereits unnötigerweise initialisiert. - Der neue Weg ():
CarJdk25
Die Validierung vonnumberOfSeats
undnumberOfDoors
kann vor demsuper()
-Aufruf stattfinden. Dies verhindert die unnötige Arbeit im Super-Konstruktor, falls die Parameter der Unterklasse ungültig sind, und macht den Kontrollfluss logischer.
Obwohl sich der Code in Vehicle
selbst kaum ändert, ermöglicht die neue Java-Version einen deutlich saubereren und effizienteren Aufbau der Konstruktoren in den davon abgeleiteten Klassen.
Ergebnis
Die Struktur des Codes kann jetzt deutlich besser dem tatsächlich gewünschten Ablauf entsprechen, sowohl bei Weiterleitung von einem Konstruktor zum anderen innerhalb einer Klasse als auch beim Aufruf des Konstruktors der Oberklasse. Der Code wird verständlicher, besser nachvollziehbar und damit auch besser wartbar.
Fazit
Java ist toll! Mit der Einführung von „Flexible Constructor Bodies“ wird die Sprache noch flexibler und benutzerfreundlicher. Diese neue Funktion ermöglicht es Entwicklern, ihren Code besser zu strukturieren und zu validieren, was sowohl zu einer höheren Codequalität als auch einer besseren Wartbarkeit führt.