Looking at Java 22: Statements before super
The first (preview) feature of Java 22 I want to talk about is one I’m quite excited about!
JEP 447 introduces a significant change by relaxing the strict rules for constructors.
It finally allows (certain) statements to be executed before calling the super(...)
call.
Table of Contents
Calling super(…) first
Inheritance plays a fundamental role in Java, yet there’s a particular constraint on constructors that can be rather bothersome: every subclass constructor must invoke super(...)
or delegate to another constructor using this(...)
.
There are instances where this rule might not seem immediately clear.
If the superclass has an argument-less constructor, either implicitly if there are no other constructors or explicitly, it gets called by any sub-class constructor even if there’s no super()
call.
From a technical perspective, the implicit super(...)
call, implicit or otherwise, is required to initialize the type above, ensuring it’s fully initialized.
Even SuperClass
calls super()
to initialize the (implicitly) extended java.lang.Object
.
Nevertheless, the call-/statement-order restriction is always there and makes certain use-/edge cases quite convoluted or even impossible, such as preparing or validating arguments before passing them to the superclass constructor.
Oftentimes, sub class constructors accept additional data or data in a different form and need to prepare or transform them.
Given the rule that the super constructor must be called first, we either need to do additional work after calling super(...)
, while calling super(...)
, or not using a constructor at all:
- Initializing first, then validating, which leads to the creation of unnecessary instances.
- Delegating to
static
method, which can make constructor invocations cumbersome and is not suitable for every scenario. - Creating
static
factory method and making the original constructorprivate
The same issues arise if we want to prepare incoming arguments further before passing them to the superclass.
We might even run into these issues without subclassing at all, as Records don’t allow any argument modification before calling this(...)
in a non-canonical constructor.
No matter which route is taken, the resulting code tends to become more complex and less intuitive than desired.
JEP 447 is going to remedy a lot of these pain points!
JEP 447: Statements before super(…)
The reason for requiring to call super(...)
first is found in the Java Language Specification (JLS §8.8.7).
Constructors are defined as follows:
Furthermore, the argument list of a constructor call runs in a static context (JLS §8.1.3) and is, therefore, quite restrictive.
JEP 447 revises the Java Language Specification (JLS) for constructors:
The change introduces a prologue of statements before an explicit constructor call. Rather than amending or changing the concept of static context, a new pre-construction context with rules similar to instance methods was added to relax one of the biggest no-nos of constructors: accessing the instance under construction.
In essence, accessing any unqualified this
expression, or using super
qualified field access, method invocation, or method reference is still forbidden.
The JEP itself describes what is available and what’s forbidden as tricky, so make sure to check out the “Description” section to get a better picture.
This is the previous PositiveBigInteger
with JEP 447 enabled:
Preparing constructor arguments becomes more readable, too:
Records and Enums also benefit from the new pre-construction context, even without supporting inheritance.
We can now use statements before calling an alternative/convenience constructor:
Conclusion
By introducing the pre-construction context, this JEP gives us an improved way of dealing with constructor invocation. Many more convoluted approaches to solving argument preparation and validation will become more straightforward and easier to grasp.
And the best thing, it doesn’t require any changes to the JVM itself, as the “call super(...)
first restriction was a historical artifact, not a JVM limitation.
That’s why relaxing the JLS was enough to give us this awesome feature!
If you want to try it out today, get yourself a copy of Java 22 and use the --enable-feature
flag on one of the tools (jshell
, java
, javac
).
Depending on which one you choose, you might need additional arguments, like --source
or --release
, but the tool itself will tell you.
Resources
Looking at Java 22
- Intro
- Unnamed Variables & Patterns (JEP 456)
- Class-File API (JEP 457)
- Foreign Function & Memory API
- [Stream Gatherers (JEP 461)]({{ ref “posts/2024/2024-04-29-looking-at-java-22-stream-gatherers.md” }})