Intro

This article is a short overview of a feature delivered as a part of Java 23 which is called Flexible constructor bodies. Java has certain restrictions when matter concerns constructing an instance of a class:

  • super() or this() has to be always called first in the constructor, it is needed to guarantee the predictable order of invocations: parent constructor first, then child’s constructor. This is required because child constructor can use fields from it’s parent, thus fields has to be ready to be used
  • when class has explicit constructor java requires that arguments cannot use the instance which is not fully constructed

These limitations create some inconveniences:

  • we cannot add validation logic before calling super() (without additional method) if we want to prevent parent class to be called in certain conditions (Fail fast approach)
  • we cannot prepare our arguments in several lines before calling super() (without additional method)
  • we cannot share argument for super class if we want to
public class Transport {
    public Transport(Clazz a, Clazz b) { ... }
}

public class SUV extends Transport {
    public  SUV(String characteristic) { this(new Clazz(characteristic)); } // only this constrcutor is visible for user, it prepares shared argument 
    private SUV(Clazz a)   { super(a, a); }     // this constructor actually instantiates 
}

The goal of this JEP is to achieve better readability and predictability while maintaining the useful top down rule of constructor invocations.

Flexible constructor solution

This feature rethinks restrictions mentioned above and introduce possibility to have code before calling super() or this(). This is achieved by getting away from treating constructor arguments as those being in static context (meaning belong to a class rather instance) and by introducing early construction context. With this new approach entire constructor block is divided into:

  • prologue - code before super() or this()
  • call of super() or this()
  • epilogue - the finishing part of constructor

It has it’s own limitation - we cannot use instance under construction:

class A {

    int i;

    A() {

        System.out.print(this);  // Error - refers to the current instance

        var x = this.i;          // Error - explicitly refers to field of the current instance
        this.hashCode();         // Error - explicitly refers to method of the current instance

        var x = i;               // Error - implicitly refers to field of the current instance
        hashCode();              // Error - implicitly refers to method of the current instance

        super();

    }

}

class B {
    int i;
    void m() { ... }
}

class C extends B {

    C() {
        var x = super.i;         // Error
        super.m();               // Error
        super();
    }

}

Complementary to this rule is a behaviour between Nested classes and Outer classes:

  • Nested class CAN refer to fields and methods of it’s Outer classes. This is because Outer class is created BEFORE Nested and thus can be used
  • Outer class CANNOT refer to fields and methods of it’s Nested classes.

Basically feature allows to do:

  • arguments validation
public class PositiveBigInteger extends BigInteger {

    public PositiveBigInteger(long value) {
        if (value <= 0) throw new IllegalArgumentException(..);
        super(value);
    }

}
  • arguments preparation
public Sub(Certificate certificate) {
        var publicKey = certificate.getPublicKey();
        if (publicKey == null) throw ...
        byte[] certBytes = switch (publicKey) {
            case RSAKey rsaKey -> ...
            case DSAPublicKey dsaKey -> ...
            default -> ...
        };
        super(certBytes );
    }
  • arguments sharing
public class SUV extends Transport {
    public SUV(String characteristic) {
        var characteristic = new Clazz(characteristic);
        super(characteristic, characteristic);
    }
}
  • arguments assignment of class’s own fields (not of parent)
class Super {

    Super() { overriddenMethod(); }

    void overriddenMethod() { System.out.println("hello"); }

}

class Sub extends Super {

    final int x;

    Sub(int x) {
        this.x = x;    // Initialize the field
        super();       // Then invoke the Super constructor explicitly
    }

    @Override
    void overriddenMethod() { System.out.println(x); }

}

NOTE: JEP also contains a warning that literary says: “As with any language change, there may be a period of pain as tools are updated” referring to deeply embedded requirement of constructor invocation has to be the first explicit/implicit call in code analyzers and style checkers.

Conclusion

Flexible constructor body is a great feature that can improve readability of our code. You can find much more detailed information at the source info

Updated: