4 Flexible Constructor Bodies

In constructors, you may add statements that don't reference the instance being created before an explicit constructor invocation.

Note:

This is a preview feature. A preview feature is a feature whose design, specification, and implementation are complete, but is not permanent. A preview feature may exist in a different form or not at all in future Java SE releases. To compile and run code that contains preview features, you must specify additional command-line options. See Preview Language and VM Features.

For background information about flexible constructor bodies, see JEP 482.

You can use this feature to prepare arguments for a superclass constructor by performing nontrivial computations or to validate arguments you want to pass to a superclass constructor. The following example validates whether the argument value is positive before passing it to the superclass constructor:

public class PositiveBigInteger extends BigInteger {

    public PositiveBigInteger(long value) {
        if (value <= 0)
            throw new IllegalArgumentException("non-positive value");
        super(Long.toString(value));
    }
}

The prologue of the constructor's body consists of the statements that appear before the super(...) invocation. The epilogue of the constructor's body consists of the statements that follow the super(...) invocation.

Early Construction Context

The early construction context of a constructor consists of the arguments to an explicit constructor invocation, such as super(...), and any statements before it.

In the previous example, the early construction context of PositiveBigInteger consists of the argument Long.toString(value) and the if-statement that checks whether value is positive.

Code in an early construction context may not access the instance under construction. This means you can’t have the following in an early construction context:

  • Any unqualified this expression: Note that you don't need to use the this keyword to access the instance under construction. For example:

    class A {
        int i;
        A() {
            // Error: Cannot reference this before supertype constructor has been
            //        called
            this.i++;
    
            // Error: cannot reference i before supertype constructor has been
            //        called        
            i++;     
            
            // Error: cannot reference this before supertype constructor has been
            //        called
            this.hashCode();
            
            // Error: cannot reference hashCode() before supertype constructor has
            //        been called
            hashCode();
            
            // Error: cannot reference this before supertype constructor has been
            //        called        
            System.out.print(this);
            super();
        }
    }
  • Any field access, method invocation, or method reference qualified by super: Again, note that you don't need to use the super keyword to access the superclass of the instance under construction:

    class D {
        int j;
    }
    
    class E extends D {
        E() {
            // Error: cannot reference super before supertype constructor has been
                      called
            super.j++;  
            
            // Error: cannot reference j before supertype constructor has been
            //        called
            j++;
            super();
        }
    }

Initializing Fields Before the super(...) Invocation

As mentioned previously, you can't read any of the fields of the current instance, whether declared in the same class as the constructor or in a superclass, until after the explicit constructor invocation. However, you can initialize fields of the current instance with the assignment operator before the super(...) invocation.

Consider the following example, which consists of two classes: Super and Sub, which extends Super and overrides Super::overridenMethod:

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

class Sub extends Super {

    final int x;
    
    Sub(int x) {
        // The Super constructor is implicitly invoked,
        // which calls overriddenMethod(), before initializing
        // the field x in Sub.
        this.x = x;
    }

    @Override
    void overriddenMethod() { System.out.println(x); }
    
    public static void main(String[] args) {
        Sub myApp = new Sub(42);
        myApp.overriddenMethod();
    }
}

The example prints the following output:

0
42

When the example invokes the constructor for Sub, it implicitly invokes the constructor for Super before assigning a value to the field x in Sub. As a result, when the example invokes Sub:overriddenMethod in the constructor for Sub, it prints the uninitialized value of x, which is 0.

You can initialize the field x in Sub and then invoke super() afterward:

class BetterSub extends Super {

    final int x;
    
    BetterSub(int x) {        
        // Initialize the int x field in BetterSub before
        // invoking the Super constructor with super().
        this.x = x;
        super();
    }

    @Override
    void overriddenMethod() { System.out.println(x); }
    
    public static void main(String[] args) {
        BetterSub myApp = new BetterSub(42);
        myApp.overriddenMethod();
    }
}

This example prints the following output:

42
42

Nested Classes

A nested class is a member of its enclosing class, which means you can't access a nested class from its enclosing class's early construction context. For example:

class B {
    
    class C { }
    
    B() {
        // Error: cannot reference this before supertype constructor has been
        //        called
        new C();
        super();
    }
}

However, a nested class's enclosing class is not one of its members, which means you can access its enclosing class from its early construction context. In the following example, both accessing F's member variable f and method hello() in the early construction context of its nested class G is permitted:

class F {
    
    int f;
    
    void hello() {
        System.out.println("Hello!");
    }
    
    class G {
        G() {
            F.this.f++;
            hello();
            super();
        }
    }
}

Records

Record constructors may not invoke super(...). However, noncanonical constructors must involve a canonical constructor by invoking this(...). Statements may appear before this(...).

Remember that a canonical constructor is a constructor whose signature is the same as the record's component list. It initializes all the component fields of the record class. Alternative or noncanonical record constructors have argument lists that don't match the record's type parameters.

In the following example, the record RectanglePair contains a noncanonical constructor, RectanglePair(Pair<Float> corner). Because it's a noncanonical constructor, it must invoke a canonical constructor with this(...). It contains several statements before this(...) that retrieve both values from the Pair<Float> parameter and validate that these values aren't negative:

record Pair<T extends Number>(T x, T y) { }

record RectanglePair(float length, float width) {
    public RectanglePair(Pair<Float> corner) {
        float x = corner.x().floatValue();
        float y = corner.y().floatValue();
        if (x < 0 || y < 0) {
            throw new IllegalArgumentException("non-positive value");
        }
        this(corner.x().floatValue(), corner.y().floatValue());
    }
}

See Alternative Record Constructors in Record Classes for more information.