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 thethis
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 thesuper
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.