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());
    }
}

詳細は、「レコード・クラス」「代替レコード・コンストラクタ」を参照してください。