In this article, you will learn:
- how to execute code in constructors before calling
super(...)
orthis(...)
from Java 22 onwards, - what restrictions exist,
- what the prologue and epilogue of a constructor are,
- and whether the new features also apply to records and enums.
Let’s take a step back: Why would you want to execute code before super(...)
or this(...)
?
Code in Constructors – Status Quo Before Java 22
The following examples show workarounds that were previously required to validate or compute parameters before calling super()
or this()
– and also what could go wrong if the constructor of the parent class calls a method that is overwritten in the child class.
Use Case 1: Parameter Validation
An everyday use case is the validation of parameters of a child class. In the following example, the constructor of Rectangle
first calls the constructor of the parent class, Shape
, and then validates and sets the width and height:
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 language: Java (java)
However, validating the parameters before the super constructor is called would be much more efficient. Yet, this is currently only possible with the following extremely unappealing workaround:
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 language: Java (java)
Use Case 2: Calculation of an Argument That Is Passed To Several Parameters
Another use case is the calculation of values that are to be passed on to more than one superclass constructor parameter. In the following example, we want to create a square with a given area (we will ignore the fact that a static factory method with a meaningful name would be more suitable than a constructor):
public class Square extends Rectangle {
public Square(Color color, int area) {
super(color, Math.sqrt(area), Math.sqrt(area));
}
}
Code language: Java (java)
To avoid calculating the square root of the area twice, we would have to introduce an auxiliary constructor:
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 language: Java (java)
However, this is only possible here because area
is of the type int
. If area
were like sideLength
of type double
, this would not work, as we would then have two constructors with identical signatures.
And if we wanted to make sure that area
is not negative beforehand, we would have to introduce a third method, as we are not allowed to execute any other code before this(...)
:
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 language: Java (java)
It is hard to see what this code does.
Use Case 3: Calling an Overridden Method in the Super Constructor
We stay with the Shape
/Rectangle
example and add a printMe()
method, which is called in the constructor of Shape
and overwritten in Rectangle
:
public class Shape {
private final Color color;
public Shape(Color color) {
this.color = color;
printMe();
}
void printMe() {
System.out.println("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;
}
@Override
void printMe() {
super.printMe();
System.out.println("width = " + width + ", height = " + height);
}
}
Code language: Java (java)
If we now call new Rectangle(Color.RED, 29.7, 21.0)
, the output is not color = RED
and width = 29.7, height = 21.0
, but:
color = RED
width = 0.0, height = 0.0
Code language: plaintext (plaintext)
The reason for this is that printMe()
is called by the Shape
constructor before width
and height
have been initialized in the Rectangle
constructor. printMe()
therefore still sees the default values of width
and height
, i.e., 0.0.
Java Code Before super(...) and this(...)
With JDK Enhancement Proposal 447, Java 22 introduces – for the time being as a preview feature and under the name “Statements before super(…)” – the possibility of executing code before calling super(...)
or this(...)
.
That means that we can now validate the area before calling this(...)
:
public class Square extends Rectangle {
public Square(Color color, int area) {
if (area < 0) throw new IllegalArgumentException(); // ⟵ Validation before `this`
this(color, Math.sqrt(area));
}
private Square(Color color, double sideLength) {
super(color, sideLength, sideLength);
}
}
Code language: Java (java)
And we no longer need the auxiliary constructor either. We can now accommodate the parameter validation and the calculation of the side length directly in the constructor:
public Square(Color color, int area) {
if (area < 0) throw new IllegalArgumentException(); // ⟵ Validation before `super`
double sideLength = Math.sqrt(area); // ⟵ Calculation before `super`
super(color, sideLength, sideLength);
}
Code language: Java (java)
With this constructor, you can see at a glance what the code does.
In Java 23, JDK Enhancement Proposal 482 introduced the possiblity to initialize fields before calling super(...)
. This allows us to write the Rectangle
class in the following way:
public class Rectangle extends Shape {
private final double width;
private final double height;
public Rectangle(Color color, double width, double height) {
this.width = width; // ⟵ Field initialization before `super`
this.height = height; // ⟵ Field initialization before `super`
super(color);
}
. . .
}
Code language: Java (java)
When calling new Rectangle(Color.RED, 29.7, 21.0)
, the printMe()
method invoked by the constructor now prints the expected result:
color = RED
width = 29.7, height = 21.0
Code language: plaintext (plaintext)
Constructor Prologue and Epilogue
The block before super(...)
or this(...)
is called the “prologue”.
Code after calling super(...)
or this(...)
or code in a constructor without calling super(...)
or this(...)
is called the “epilogue”.
Restrictions
In the prologue, the code may initialize fields but must not read any fields of the class and must not call any non-static methods of the class. It must also not create instances of non-static inner classes, as these would then have a reference to the potentially uninitialized parent object.
The prologue of the constructor of an inner class, on the other hand, can access fields and methods of the outer class without restriction.
Records and Enums
Records and enums cannot have a parent class, but their constructors can call alternative constructors with this(...)
.
Code that complies with the abovementioned restrictions may now also be executed before that.
Conclusion
Calling code before super(...)
or this(...)
allows fields to be initialized and parameters to be validated or calculated before the super constructor or an alternative constructor is called. That makes the code safer and enables much more expressive code than the workarounds we had to construct in the past.
Have you also had to implement complicated workarounds, and what do you think of the new feature? Let me know via the comment function!
You don’t want to miss any HappyCoders.eu article and always be informed about new Java features? Then click here to sign up for the free HappyCoders newsletter.