5 柔軟なコンストラクタ本体
コンストラクタでは、明示的なコンストラクタ呼出しの前に、作成されるインスタンスを参照しない文を追加できます。
ノート:
これはプレビュー機能です。プレビュー機能は、設計、仕様および実装が完了したが、永続的でない機能です。プレビュー機能は、将来のJava SEリリースで、異なる形式で存在することもあれば、まったく存在しないこともあります。プレビュー機能が含まれているコードをコンパイルして実行するには、追加のコマンド行オプションを指定する必要があります。『Preview Language and VM Features』を参照してください。柔軟なコンストラクタ本体の背景情報は、JEP 492を参照してください。
この機能を使用すると、自明でない計算を実行することでスーパークラス・コンストラクタの引数を準備したり、スーパークラス・コンストラクタに渡す引数を検証したりできます。次の例では、引数value
をスーパークラス・コンストラクタに渡す前に、正かどうかを検証します。
public class PositiveBigInteger extends BigInteger {
public PositiveBigInteger(long value) {
if (value <= 0)
throw new IllegalArgumentException("non-positive value");
super(Long.toString(value));
}
}
コンストラクタの本体のプロローグは、super(...)
呼出しの前に出現する文で構成されます。コンストラクタの本体のエピローグは、super(...)
呼出しに続く文で構成されます。
早期構築コンテキスト
コンストラクタの早期構築コンテキストは、super(...)
などの明示的なコンストラクタ呼出しの引数と、その前の文で構成されます。
前の例では、PositiveBigInteger
の早期構築コンテキストは、引数Long.toString(value)
と、value
が正かどうかをチェックするif
文で構成されます。
早期構築コンテキスト内のコードは、構築中のインスタンスにはアクセスできません。つまり、早期構築コンテキストでは次のものを使用できません:
-
修飾されていない
this
式:this
キーワードを使用して、構築中のインスタンスにアクセスする必要はありません。たとえば: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(); } }
-
super
で修飾されたフィールド・アクセス、メソッド呼出しまたはメソッド参照: 同様に、super
キーワードを使用して、構築中のインスタンスのスーパークラスにアクセスする必要はありません。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(); } }
super(...)呼出し前のフィールドの初期化
前述のように、現在のインスタンスのフィールドは、コンストラクタと同じクラスで宣言されたかスーパークラスで宣言されたかに関係なく、コンストラクタの明示的な呼出しの後まで読み取れません。ただし、super(...)
の呼出し前に現在のインスタンスのフィールドを代入演算子で初期化できます。
次の例について考えてみます。この例は、Super
と、Super
を拡張してSuper::overridenMethod
をオーバーライドするSub
という2つのクラスで構成されます:
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();
}
}
この例の出力は次のとおりです:
0
42
Sub
のコンストラクタを呼び出すと、Sub
のフィールドx
に値を割り当てる前に、Super
のコンストラクタが暗黙的に呼び出されます。その結果、Sub
のコンストラクタでSub:overriddenMethod
を呼び出すと、x
の初期化されていない値(0
)が出力されます。
Sub
のフィールドx
を初期化した後、super()を呼び出すことができます:
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();
}
}
この例の出力は次のとおりです。
42
42
ネストされたクラス
ネストされたクラスは、その包含クラスのメンバーです。つまり、その包含クラスの早期構築コンテキストからネストされたクラスにアクセスすることはできません。たとえば:
class B {
class C { }
B() {
// Error: cannot reference this before supertype constructor has been
// called
new C();
super();
}
}
ただし、ネストされたクラスの包含クラスは、そのメンバーの1つではありません。つまり、その包含クラスには、その早期構築コンテキストからアクセスできます。次の例では、ネストされたクラスG
の早期構築コンテキストでF
のメンバー変数f
とメソッドhello()
の両方にアクセスできます:
class F {
int f;
void hello() {
System.out.println("Hello!");
}
class G {
G() {
F.this.f++;
hello();
super();
}
}
}
レコード
レコード・コンストラクタは、super(...)
を呼び出すことはできません。ただし、this(...)
を呼び出して、非標準コンストラクタに標準コンストラクタを含める必要があります。文は、this(...)
の前に記述できます。
標準コンストラクタは、レコードのコンポーネント・リストと同じシグネチャを持つコンストラクタであることに注意してください。レコード・クラスのすべてのコンポーネント・フィールドが初期化されます。代替または非標準のレコード・コンストラクタには、レコードの型パラメータと一致しない引数リストがあります。
次の例では、レコードRectanglePair
に非標準コンストラクタRectanglePair(Pair<Float> corner)
が含まれています。これは非標準コンストラクタであるため、this(...)
を使用して標準コンストラクタを呼び出す必要があります。this(...)
の前に、Pair<Float>
パラメータから両方の値を取得し、これらの値が負でないことを検証するいくつかの文が含まれています。
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());
}
}
詳細は、「レコード・クラス」の「代替レコード・コンストラクタ」を参照してください。