柔軟なコンストラクタ本体(第3プレビュー)

Java®言語仕様の変更点・バージョン24+36-3646

このドキュメントでは、Java SE 24の提案された機能である柔軟なコンストラクタ本体をサポートするために、Java言語仕様に加えられた変更について説明します。この機能の概要は、JEP 492を参照してください。

変更は、JLSの既存のセクションについて説明しています。新しいテキストはこのように示され、削除されたテキストはこのように示されます。必要に応じて、説明と考察が端の方にグレーのボックスで囲まれて記載されています。

変更ログ:

2024-12-18: 15.9.2の小さなバグが修正されました。8.8.7.1への小規模な編集上の変更。

2024-12-05: 8.8.7.1の小さなバグが修正されました。

2024-11-01: 第3プレビューの初稿。次のものを含む小さなバグの修正のみ:

第6章: 名前

6.5 名前の意味の確認

6.5.6 式名の意味

6.5.6.1 単純な式名

式名が単一の識別子からなる場合は、その識別子が出現した時点でスコープ内にあるローカル変数、仮パラメータ、例外パラメータ、またはフィールドのいずれかを示す1つの宣言が存在する必要があります。そうでない場合、コンパイル時にエラーが発生します。

宣言でクラスCのインスタンス変数が示されている場合は(8.3.1.1)、次の両方すべてに当てはまる必要があります。そうでないと、コンパイル時にエラーが発生します。

たとえば、式名は、Cによって宣言されたstaticメソッドの本体や、C内にネストされたstaticクラスのインスタンス・メソッドの本体には出現できません。

宣言でローカル変数、仮パラメータまたは例外パラメータが示されている場合に、Xは、ローカル変数またはパラメータ宣言を含む、最も内側のメソッド宣言、コンストラクタ宣言、インスタンス・イニシャライザ、静的イニシャライザ、フィールド宣言または明示的なコンストラクタ呼出しであるとします。Xで直接宣言されたローカル・クラス、ローカル・インタフェースまたは無名クラスDの本体に式名が直接的または間接的に現れる場合は、次の両方に当てはまる必要があり、そうでないとコンパイル時にエラーが発生します。

たとえば、式名は、Dによって宣言されたstaticメソッドの本体にも、(Dがローカル・インタフェースの場合) Dのデフォルト・メソッドの本体にも出現しないようにする必要があります。

宣言でfinalでも事実上finalでもない(4.12.4)ローカル変数、仮パラメータまたは例外パラメータが示されている場合は、式名が、Xによって直接的または間接的に含まれる内部クラスに、またはXによって含まれるラムダ式(15.27)に出現すると、コンパイル時にエラーが発生します。

これらのルールの正味の効果は、(i)参照が静的コンテキストになく、(ii)参照から変数宣言への内部(staticでない)クラスのチェーンがあり、(iii)変数がfinalまたは事実上finalである場合に、ローカル変数、仮パラメータまたは例外パラメータを、そのスコープ内で宣言されたネストされたクラスまたはインタフェースからのみ参照できるということです。ラムダ式からの参照では、変数がfinalまたは事実上finalである必要があります。

宣言により、単純な式の前に明確に割り当てられるfinal変数が宣言される場合、この名前はその変数の値を意味します。そうでない場合、式名は、その宣言によって宣言された変数を意味します。

式名が割当てコンテキスト、呼出しコンテキスト、またはキャスト・コンテキストに出現する場合、式名の型はキャプチャ変換後のフィールド、ローカル変数、またはパラメータになります(5.1.10)。

そうでない場合、式名の型はフィールド、ローカル変数、またはパラメータの宣言された型になります。

つまり、式名が「右側」に出現する場合、その型はキャプチャ変換の対象になります。式名が「左側」に出現する変数である場合、その型はキャプチャ変換の対象にはなりません。

例6.5.6.1-1.単純な式名

class Test {
    static int v;
    static final int f = 3;
    public static void main(String[] args) {
        int i;
        i = 1;
        v = 2;
        f = 33;  // compile-time error
        System.out.println(i + " " + v + " " + f);
    }
}

このプログラムでは、ivおよびfへの割当てで左側として使用されている名前は、ローカル変数i、フィールドvおよびfの値(fは変数finalであるため、変数fではありません)を示しています。したがって、この例では、最後の割当ての左側に変数がないため、コンパイル時にエラーが発生します。間違った割当てを削除すると、修正したコードをコンパイルできるようになり、次の出力が生成されます。

1 2 3

例6.5.6.1-2.インスタンス変数への参照

class Test {
    static String a;
    String b;

    String concat1() {
        return a + b;
    }

    static String concat2() {
        return a + b;  // compile-time error
    }

    int index() {
        interface I {
            class Matcher {
                void check() {
                    if (a == null ||
                        b == null) {  // compile-time error
                        throw new IllegalArgumentException();
                    }
                }
                int match(String s, String t) {
                    return s.indexOf(t);
                }
            }
    }

    I.Matcher matcher = new I.Matcher();
    matcher.check();
    return matcher.match(a, b);
    }
}

フィールドaおよびbはクラスTestの本体全体でスコープ内にあります。ただし、concat2メソッドの静的コンテキスト内、またはTestの内部クラスではないネストされたクラスMatcherの宣言におけるbという名前の使用は無効です。

例6.5.6.1-3.ローカル変数および仮パラメータへの参照

class Test {
    public static void main(String[] args) {
        String first = args[0];

        class Checker {
            void checkWhitespace(int x) {
                String arg = args[x];
                if (!arg.trim().equals(arg)) {
                    throw new IllegalArgumentException();
                }
            }

            static void checkFlag(int x) {
                String arg = args[x];  // compile-time error
                if (!arg.startsWith("-")) {
                    throw new IllegalArgumentException();
                }
            }

            static void checkFirst() {
                Runnable r = new Runnable() {
                    public void run() {
                        if (first == null) {  // compile-time error
                            throw new IllegalArgumentException();
                        }
                    }
                };
                r.run();
            }
        }

        final Checker c = new Checker();
        c.checkFirst();
        for (int i = 1; i < args.length; i++) {
            Runnable r = () -> {
                c.checkWhitespace(i);  // compile-time error
                c.checkFlag(i);  // compile-time error
            };
        }
    }
}

仮パラメータargsは、メソッドmainの本体全体でスコープ内にあります。argsは実質的にfinalであるため、名前argsをローカル・クラスCheckerのインスタンス・メソッドcheckWhitespaceで使用できます。ただし、名前argsをローカル・クラスCheckercheckFlagメソッドの静的コンテキストで使用することは不正です。

ローカル変数firstは、メソッドmainの本体の残りの部分のスコープ内にあります。firstも実質的にfinalです。ただし、checkFirstで宣言された無名クラスはCheckerの内部クラスではないため、名前firstを無名クラス本体で使用することはできません。(ラムダ式は静的コンテキストにおいて出現するため、checkFirstの本体におけるラムダ式でもfirstを参照できません。)

ローカル変数cは、メソッドmainの本体の最後の数行のスコープ内にあり、finalと宣言されているため、名前cをラムダ式の本体で使用できます。

ローカル変数iforループ全体でスコープ内にあります。ただし、iは実質的にfinalではないため、ラムダ式の本体でiという名前を使用することは不正になります。

6.5.7 メソッド名の意味

6.5.7.1 単純なメソッド名

単純なメソッド名はメソッド呼出し式(15.12)のコンテキストに出現します。単純なメソッド名は、呼び出されるメソッドの名前を示す単一のUnqualifiedMethodIdentifierからなります。メソッド呼出しのルールでは、UnqualifiedMethodIdentifierで、メソッド呼出しの時点でスコープ内にあるメソッドが示されている必要があります。また、このルール(15.12.3)では、静的コンテキスト(8.1.3)において、またはインスタンス・メソッドを宣言するクラスまたはインタフェースの内部クラスでないネストされたクラスまたはインタフェースにおいて出現するインスタンス・メソッドへの参照は禁止されています。ルール(15.12.3)では、次のいずれかで出現するインスタンス・メソッドへの参照も禁止されています。

  1. 静的コンテキスト(8.1.3)、

  2. インスタンス・メソッドがメンバーである最も内側のクラスまたはインタフェースの内部クラスでない、ネストされたクラスまたはインタフェース、または

  3. インスタンス・メソッドがメンバーであるクラスの早期構築コンテキスト(8.8.7)。

編集部: 前述の抹消線を引いた文で示されている、(15.12.3)にあるルールの概要は正しくありません。これは正しくは、次のことが禁止されているということです。

class T {
    void foo() { ... }
    static class Mid {
        class L {
            ... foo() ... // Compile-time error as L is not an inner class of T
        }
    }
}

次のコードは(15.12.3)のルールでは許可されないということを示唆しています。

class B {
    void foo() { ... }
}
class T extends B {
    class Mid {
        class L {
            ... foo() ... // No compile-time error even though L is not an inner class of B
        }
    }
 }

例6.5.7.1-1.単純なメソッド名

次のプログラムは、呼び出すメソッドを決定する場合にスコープが果たす役割を示しています。

class Super {
    void f2(String s)       {}
    void f3(String s)       {}
    void f3(int i1, int i2) {}
}

class Test {
    void f1(int i) {}
    void f2(int i) {}
    void f3(int i) {}

    void m() {
        new Super() {
            {
                f1(0);  // OK, resolves to Test.f1(int)
                f2(0);  // compile-time error
                f3(0);  // compile-time error
            }
        };
    }
}

f1(0)の呼出しでは、f1という名前のメソッドのみがスコープ内になります。これはメソッドTest.f1(int)であり、その宣言は匿名クラス宣言を含め、Test本体全体でスコープ内になります。15.12.1では匿名クラス宣言にf1という名前のメンバーがないため、クラスTestでの検索が選択されます。最終的にTest.f1(int)が解決されます。

f2(0)の呼出しでは、f2という名前の2つのメソッドがスコープ内になります。まず、メソッドSuper.f2(String)の宣言が匿名クラス宣言全体でスコープ内になります。次に、メソッドTest.f2(int)の宣言が匿名クラス宣言を含め、Test本体全体でスコープ内になります。(それぞれが宣言された時点では、もう一方はスコープ内にはないため、どちらの宣言ももう一方をシャドウ化することはありません。) 15.12.1ではf2という名前のメンバーがあるためクラスSuperでの検索が選択されます。ただし、Super.f2(String)f2(0)には適用できないため、コンパイル時にエラーが発生します。クラスTestは検索されないことに注意してください。

f3(0)の呼出しでは、f3という名前の3つのメソッドがスコープ内になります。最初と2番目は、メソッドSuper.f3(String)およびSuper.f3(int,int)の宣言が匿名クラス宣言全体でスコープ内になります。3番目に、メソッドTest.f3(int)の宣言が匿名クラス宣言を含め、Test本体全体でスコープ内になります。15.12.1では、f3という名前のメンバーがあるためクラスSuperでの検索が選択されます。ただし、Super.f3(String)およびSuper.f3(int,int)f3(0)には適用できないため、コンパイル時にエラーが発生します。クラスTestは検索されないことに注意してください。

語彙的な包含スコープの前でネストされたクラスのスーパークラス階層の検索を選択することは、「コーム・ルール」と呼ばれます(15.12.1)。

第8章: クラス

クラス宣言では、新規クラスを定義し、その実装方法について記述します(8.1)。

最上位クラス(7.6)は、コンパイル・ユニットで直接宣言されたクラスです。

ネストしたクラスは、宣言が別のクラスまたはインタフェース宣言の本体内で行われるクラスです。ネストしたクラスは、メンバー・クラス(8.59.5)、ローカル・クラス(14.3)または無名クラス(15.9.5)である場合があります。

ネストしたクラスの一部の種類は内部クラス(8.1.3)であり、これは、包含クラス・インスタンス、ローカル変数および型変数を参照できるクラスです。

ネストされたクラスは内部(8.1.3)である場合があります。その場合は、それで、(その宣言の出現位置に応じて)包含するクラスのインスタンス、ローカル変数および型変数を参照できることがあります。

enumクラス(8.9)は、名前付きクラス・インスタンスの小さいセットを定義する省略構文で宣言されたクラスです。

レコード・クラス(8.10)は、値の単純集計を定義する省略構文で宣言されたクラスです。

この章では、すべてのクラスに共通するセマンティクスについて説明します。特定の種類のクラスに固有の詳細は、これらのコンストラクトに特化したセクションで説明します。

クラスがpublic (8.1.1)として宣言される場合、そのモジュールの任意のパッケージ内のコードから、および場合によっては他のモジュール内のコードから参照できます。

クラスはabstract (8.1.1.1),として宣言でき、これが不完全に実装されている場合はabstractとして宣言する必要があります。このようなクラスはインスタンス化できませんが、サブクラスによって拡張できます。クラスを拡張できる程度は、明示的に制御できます(8.1.1.2)。ここでは、サブクラスを制限するためにsealedと宣言することも、サブクラスがないことを保証するためにfinalと宣言することもできます。Objectを除く各クラスは、既存の単一のクラスの拡張(つまり、サブクラス) (8.1.4)であり、インタフェースを実装する場合があります(8.1.5)。

クラスは汎用(8.1.2)である場合があります。つまり、クラスは、クラスの様々なインスタンス間でバインディングが異なる可能性のある型変数を宣言しています。

クラス宣言は、他の種類の宣言と同じように、注釈(9.7)を使用して修飾できます。

クラスの本体は、メンバー(フィールド、メソッド、クラスおよびインタフェース)、インスタンスと静的イニシャライザ、およびコンストラクタ(8.1.7)を宣言します。メンバー(8.2)のスコープ(6.3)は、メンバーが属するクラスの宣言の本体全体です。フィールド、メソッド、メンバー・クラス、メンバー・インタフェースおよびコンストラクタ宣言には、アクセス修飾子publicprotectedまたはprivateが含まれる場合があります(6.6)。クラスのメンバーには、宣言されたメンバーと継承されたメンバーの両方が含まれます(8.2)。新しく宣言されたフィールドは、スーパークラスまたはスーパーインタフェース内で宣言されたフィールドを隠すことができます。新しく宣言されたメンバー・クラスおよびメンバー・インタフェースは、スーパークラスまたはスーパーインタフェース内で宣言されたメンバー・クラスおよびメンバー・インタフェースを隠すことができます。新しく宣言されたメソッドは、スーパークラスまたはスーパーインタフェース内で宣言されたメソッドを隠す、実装する、またはオーバーライドすることができます。

フィールド宣言(8.3)では、1回インカネーションされるクラス変数、およびクラスのインスタンスごとに新しくインカネーションされるインスタンス変数を記述します。フィールドはfinal (8.3.1.2)として宣言できます。この場合、このフィールドは1回のみ割り当てることができます。任意のフィールド宣言にイニシャライザを含めることができます。

メンバー・クラス宣言(8.5)では、前後のクラスのメンバーであるネストしたクラスを記述します。メンバー・クラスはstaticである場合があります。この場合、メンバー・クラスは、前後のクラスのインスタンス変数にアクセスできません。または、内部クラスである場合もあります。

メンバー・インタフェース宣言(8.5)では、前後のクラスのメンバーであるネストしたインタフェースを記述します。

メソッド宣言(8.4)では、メソッド呼出し式(15.12)によって呼び出すことのできるコードを記述します。クラス・メソッドはクラスに関連して呼び出され、インスタンス・メソッドは、クラスのインスタンスである特定のオブジェクトに関連して呼び出されます。宣言が実装方法を示していないメソッドは、abstractとして宣言する必要があります。メソッドはfinal (8.4.3.3)として宣言される場合があります。この場合、このメソッドは隠したりオーバーライドすることができません。メソッドは、プラットフォーム依存のnativeコード(8.4.3.4)によって実装される場合があります。synchronizedメソッド(8.4.3.6)は、synchronized文(14.19)で使用されるように、本体を実行する前にオブジェクトを自動的にロックし、戻り時にオブジェクトを自動的にロック解除します。これにより、そのアクティビティを他のスレッド(17)のアクティビティと同期できるようになります。

メソッド名はオーバーロードする場合があります(8.4.9)。

インスタンス・イニシャライザ(8.6)は、作成時にインスタンスの初期化をサポートするために使用できる実行可能コードのブロックです(15.9)。

静的イニシャライザ(8.7)は、クラスの初期化をサポートするために使用できる実行可能コードのブロックです。

コンストラクタ(8.8)はメソッドと似ていますが、メソッド呼出しによって直接呼び出すことはできません。コンストラクタは、新しいクラス・インスタンスを初期化するために使用されます。メソッドと同様、これはオーバーロードする場合があります(8.8.8)。

8.1 クラス宣言

8.1.3 内部クラスと包含インスタンス

内部クラスは、明示的にも暗黙的にもstaticでない、ネストされたクラスです。

内部クラスは次のいずれかです。

次のネストされたクラスは、暗黙的にstaticであるため、内部クラスではありません。

ネストされたクラスに適用されるルールはすべて、内部クラスに適用されます。具体的に述べると、内部クラス自体がstaticでない場合でも、内部クラスではstaticメンバー(8.2)が宣言され継承され、静的イニシャライザ(8.7)が宣言される場合があります。

ネストされたインタフェースはすべて暗黙的にstatic (9.1.1.3)であるため、"内部インタフェース"はありません。

例8.1.3-1.内部クラス宣言とstaticメンバー

class HasStatic {
    static int j = 100;
}

class Outer {
    class Inner extends HasStatic {
        static {
            System.out.println("Hello from Outer.Inner");
        }

        static       int x = 3;
        static final int y = 4;

        static void hello() {
            System.out.println("Hello from Outer.Inner.hello");
        }

        static class VeryNestedButNotInner
            extends NestedButNotInner {}
    }

    static class NestedButNotInner {
        int z = Inner.x;
    }

    interface NeverInner {}  // Implicitly static, so never inner
}

Java SE 16より前は、内部クラスでは静的イニシャライザを宣言できず、定数変数(4.12.4)であるstaticメンバーのみを宣言できました。

構成要素(文、ローカル変数宣言文、ローカル・クラス宣言、ローカル・インタフェース宣言または式)は、最も内側にある場合、静的コンテキストで出現します。

この構成要素を含むのは、次のいずれかです。

なお、コンストラクタ宣言またはインスタンス・イニシャライザにおいて出現する構成要素は、静的コンテキストにおいては出現しません。

静的コンテキストの目的は、静的コンテキストを宣言で字句として包含しているクラスの現行インスタンスを明示的や暗黙的に参照することが禁じられているコードの区別です。この場合、静的コンテキストを宣言で字句として包含しているクラスの、定義されている現行インスタンスはありません。したがって、静的コンテキストにおいて出現するコードには、次のような制約があります。

Oが直前と直後のCのクラスまたはインタフェース宣言で、Cの宣言が静的コンテキストに出現しない場合、内部クラスCクラスまたはインタフェースOの直接の内部クラスです。

内部クラスがローカル・クラスまたは無名クラスである場合は静的コンテキストで宣言でき、その場合、包含クラスまたはインタフェースの内部クラスとみなされることはありません。

クラスCクラスまたはインタフェースOの内部クラスとなるのは、それがOの直接内部クラスであるか、Oの内部クラスの内部クラスである場合です。

これは正常な状況ではありませんが、内部クラスの直前と直後のクラスまたはインタフェース宣言がインタフェースの場合に該当する場合があります。これはdefaultまたはstaticメソッド本体においてクラスがローカルまたは無名クラスとして宣言された場合のみ発生します(9.4)。

クラスまたはインタフェースOは、それ自体の、ゼロ番目の、字句として包含するクラスまたはインタフェースの宣言です。

クラスOが、クラスCの、n番目の字句的に包含するクラスの宣言となるのは、それがCの、n-1番目の字句的に包含するクラスの宣言の、直接包含するクラスの宣言である場合です。

クラスまたはインタフェースOの直接内部クラスCのインスタンスiOのインスタンスに関連付けられる場合があります(iの直接包含インスタンスと呼ばれる)。オブジェクトを直接包含するインスタンス(ある場合)は、オブジェクトの作成時に特定されます(15.9.2)。

オブジェクトoは、それ自体のゼロ番目の字句的な包含インスタンスです。

オブジェクトoインスタンスiのn番目の字句的な包含インスタンスとなるのは、それがin-1番目の字句的な包含インスタンスを直接包含するインスタンスである場合です。

宣言が静的コンテキストにおいて出現する内部ローカル・クラスまたは無名クラスのインスタンスには、直接包含インスタンスはありません。また、ネストされたstaticクラスのインスタンス(8.1.1.4)には、直接包含インスタンスはありません。

編集部: 既存のJLSでは、早期コンストラクタ・コンテキストと呼ばれるようになったものも静的コンテキストを使用することでカバーしています。これは、たとえば、明示的なコンストラクタ呼出しの引数として宣言された匿名クラスは静的コンテキスト内に示されるとみなされていることを意味します。次に例を示します。

class Outer {
    int x;
    class Inner extends Thread {
        Inner() {
            super(new Runnable() {
                @Override
                public void run() {
                    System.out.println("hello from " + Outer.this.x); // error??
                }
            });
        }
    }
}

既存のJLSでは、匿名クラスには直接包含するインスタンスがないため、参照のOuter.this.xが有効かどうかは明確ではありません。(参照コンパイラは、このコードを受け入れます。)

早期構築コンテキストに示されるローカルおよび匿名のクラスは、常に直接包含するインスタンスを持つように定義されるようになりました。ルールでは、特定のクラスの早期構築コンテキストから参照できるものを指定します。前述の例では、匿名クラスはクラスOuterの早期構築コンテキストにはないため、フィールドxは参照できます。

それ自体がクラスまたはインタフェースSOの直接内部クラスであるCのそれぞれのスーパークラスSについては、Sに関してiを直接包含するインスタンスと呼ばれる、iに関連付けられたSOのインスタンスがあります。そのクラスの直接スーパークラス(存在する場合)に関わるオブジェクトの直接包含インスタンスは、明示的コンストラクタ呼出し(8.8.7.1)を介してスーパークラス・コンストラクタが呼び出されるときに特定されます。

(宣言が静的コンテキストには出現しない)内部クラスが語彙的な包括クラスまたはインタフェース宣言のメンバーであるインスタンス変数を参照すると、対応する語彙的な包括インスタンスの変数が使用されます。

内部クラスに、字句として包含するクラスまたはインタフェースの宣言のメンバーであるインスタンス変数への有効な参照が含まれている場合は、対応する字句的に包含するインスタンスの変数が使用されます。

内部クラスで使用されているが宣言されていないローカル変数、仮パラメータまたは例外パラメータは、6.5.6.1で示されているとおり、finalまたは事実上final (4.12.4)である必要があります。

内部クラスで使用されるが、宣言されていないローカル変数は、内部クラスの本体より前で明確に割り当てる必要があります(16)。そうしないとコンパイル時にエラーが発生します。

変数の使用に関する同様のルールがラムダ式(15.27.2)の本体にも適用されます。

語彙的な包含クラスまたはインタフェース宣言の空のfinalフィールド(4.12.4)は内部クラス内では割り当てることはできず、そうでない場合はコンパイル時にエラーが発生します。

例8.1.3-2.内部クラス宣言

class Outer {
    int i = 100;
    static void classMethod() {
        final int l = 200;
        class LocalInStaticContext {
            int k = i;  // Compile-time error
            int m = l;  // OK
        }
    }
    void foo() {
        class Local {  // A local class
            int j = i;
        }
    }
}

クラスLocalInStaticContextの宣言は、静的メソッドclassMethod内にあるため、静的コンテキストに出現します。クラスOuterのインスタンス変数は、静的メソッドの本体内では使用できません。具体的には、Outerのインスタンス変数をLocalInStaticContextの本体内で使用することはできません。ただし、前後のメソッドからのローカル変数はエラーなしで参照できます(それらがfinalとして宣言されているか、実質的にfinalである場合)。

宣言が静的コンテキストには出現しない内部クラスは、包含クラス宣言のインスタンス変数を自由に参照できます。インスタンス変数は常にインスタンスに関して定義されます。包含クラス宣言のインスタンス変数の場合、インスタンス変数は内部クラスの包含インスタンスに関して定義される必要があります。たとえば、前述のクラスLocalには、クラスOuterの包含インスタンスがあります。さらに、次のような例があるとします。

class WithDeepNesting {
    boolean toBe;
    WithDeepNesting(boolean b) { toBe = b; }

    class Nested {
        boolean theQuestion;
        class DeeplyNested {
            DeeplyNested(){
                theQuestion = toBe || !toBe;
            }
        }
    }
}

ここでは、WithDeepNesting.Nested.DeeplyNestedのそれぞれのインスタンスに、クラスWithDeepNesting.Nestedの包含インスタンス(それを直接包含するインスタンス)とクラスWithDeepNestingの包含インスタンス(その2番目の字句的な包含インスタンス)があります。

8.8 コンストラクタ宣言

8.8.7 コンストラクタ本体

コンストラクタ本体は、クラスの新しいインスタンスを作成するプロセス(12.5)の一部として実行されるコードのブロックです。コンストラクタ本体には、同じクラスの、または直接スーパークラスの別のコンストラクタの明示的な呼出し(8.8.7.1)が含まれている場合があります。

コンストラクタ本体の最初の文は、同じクラスの、または直接スーパークラスの別のコンストラクタの明示的な呼出し(8.8.7.1)である場合があります。

ConstructorBody:
{ [ExplicitConstructorInvocation] [BlockStatements] }
{ [BlockStatements] }
{ [BlockStatements] ExplicitConstructorInvocation [BlockStatements] }

コンストラクタで、thisが関わる一連の1つ以上の明示的コンストラクタ呼出しによって直接的または間接的にそのコンストラクタ自体を呼び出すと、コンパイル時にエラーが発生します。

編集部: 上の段落は、8.8.7.1に移動しました

コンストラクタ本体に明示的コンストラクタ呼出しが含まれている場合は、その明示的コンストラクタ呼出しの前のBlockStatementsを、コンストラクタ本体のプロローグと呼びます。コンストラクタ本体のプロローグは空の場合があります。明示的コンストラクタ呼出しがないコンストラクタでのBlockStatementsと、コンストラクタ本体における明示的コンストラクタ呼出しの後のBlockStatementsを、エピローグと呼びます。コンストラクタ本体のエピローグも空の場合があります。

コンストラクト(文、ローカル変数宣言文、ローカル・クラス宣言、ローカル・インタフェース宣言または式)は、Cのコンストラクタ本体のプロローグに含まれている場合、またはCのコンストラクタ本体の明示的な呼出し(8.8.7.1)内にネストされている場合に、クラスCの早期構築コンテキスト内に示されます。

コンストラクタ本体が明示的なコンストラクタ呼出しを開始せず含まず、宣言されるコンストラクタが大もとのクラスObjectの一部でない場合、コンストラクタ本体には、(1)空のプロローグがあり、(2)スーパークラス・コンストラクタ呼出し"super();" (引数をとらないそのその直接スーパークラスのコンストラクタの呼出し)で暗黙的に始まり、(3)コンストラクタ本体で指定された文(ある場合)は、コンストラクタ本体のエピローグを形成するために使用されます

明示的または暗黙的コンストラクタ呼出しの可能性、値を明示的に返すことの禁止return文の禁止(14.17)を除き、コンストラクタの本体はメソッドの本体(8.4.7)と似ています。

なお、コンストラクタ本体には、最大でも1つの明示的コンストラクタ呼出しが含まれます。この構文では、たとえば、明示的コンストラクタ呼出しをif文の異なるブランチに配置することは不可能です。

return文(14.17)は、それに式が含まれていない場合は、コンストラクタの本体で使用できます。

例8.8.7-1.コンストラクタ本体

class Point {
    int x, y;
    Point(int x, int y) { this.x = x; this.y = y; }
}
class ColoredPoint extends Point {
    static final int WHITE = 0, BLACK = 1;
    int color;
    ColoredPoint(int x, int y) {
        this(x, y, WHITE);
    }
    ColoredPoint(int x, int y, int color) {
        super(x, y);
        this.color = color;
    }
}

ここでは、最初のコンストラクタのColoredPointで、追加の引数を指定して2番目が呼び出されています。2番目のコンストラクタのColoredPointで、座標を渡してそのスーパークラスPointのコンストラクタが呼び出されています。

8.8.7.1 明示的なコンストラクタ呼出し
ExplicitConstructorInvocation:
[TypeArguments] this ( [ArgumentList] ) ;
[TypeArguments] super ( [ArgumentList] ) ;
ExpressionName . [TypeArguments] super ( [ArgumentList] ) ;
Primary . [TypeArguments] super ( [ArgumentList] ) ;

便宜上、ここでは4.5.1および15.12の次のプロダクションを示します。

TypeArguments:
< TypeArgumentList >
ArgumentList:
Expression {, Expression}

明示的コンストラクタ呼出し文呼出しは、次の2種類に分けられます。

コンストラクタで一連の1つ以上の代替コンストラクタ呼出しによって直接的または間接的にそれ自体を呼び出すと、コンパイル時にエラーが発生します。

明示的コンストラクタ呼出し文では、現在のオブジェクトを参照する構成要素の使用を制限する、静的コンテキスト(8.1.3)が導入されます。特に、キーワードthisおよびsuperは、静的コンテキスト(15.8.315.11.2)では禁止されています。これは、字句として囲んでいる宣言(6.5.5.16.5.6.115.12.3)のインスタンス変数、インスタンス・メソッドおよび型パラメータに対する参照が修飾されていないためです。

明示的なコンストラクタ呼出しでは、現在のオブジェクトを参照する構造体の使用を制限する、早期構築コンテキスト(8.8.7)が導入されます。特に、thisおよびsuperを使用する現在のオブジェクトへの参照は、インスタンス変数およびインスタンス・メソッド(6.5.6.16.5.7.1)への参照と同様に、早期構築コンテキスト(15.8.315.11.2)で制限されます。

TypeArgumentsthisまたはsuperの左側にある場合、いずれかの型引数がワイルドカードであれば(4.5.1)、コンパイル時にエラーが発生します。

Cはインスタンス化されるクラスであり、SCの直接スーパークラスであるとします。

スーパークラス・コンストラクタ呼出しが修飾されていない場合は、次のようになります。

編集部: 前述のアサーションは、JEP 395で導入された拡張機能のバグ修正です。

スーパークラス・コンストラクタ呼出しが修飾されている場合は、次のようになります。

iを、スーパークラス・コンストラクタ呼出しによって作成されるインスタンスにします。S (ある場合)に関してiを直接包含するインスタンスは次のように決定されます:

スーパークラス・コンストラクタ呼出しに即時包含インスタンスがある場合、このインスタンスはコンストラクタ呼出しの最初の実際の引数とみなされ、コンストラクタ呼出しに対する後続の実際の引数は、スーパークラス・コンストラクタ呼出しの引数リスト(ある場合)内の引数とみなされます(出現する順序で)。それ以外の場合、コンストラクタ呼出しの実際の引数は、スーパークラス・コンストラクタ呼出しの引数(ある場合)として、出現順に受け取られます。

明示的コンストラクタ呼出しでスローできる例外型については、11.2.2で示します。

代替コンストラクタ呼出しの評価では、まず、通常のメソッド呼出しと同様に、コンストラクタの引数が左から右に評価され、続いてコンストラクタが呼び出されます。

スーパークラス・コンストラクタ呼出しの評価は、次のように進められます:

  1. iは、作成されるインスタンスであるとします。S (ある場合)に関してiを直接包含するインスタンスを特定する必要があります。

    • Sが内部クラスでない場合、またはSの宣言が静的コンテキストに出現する場合、Sに関してiを直接包含するインスタンスは存在しません。

    • そうでない場合、スーパークラス・コンストラクタ呼出しが非修飾であれば、Sは内部ローカル・クラスまたは内部メンバー・クラスである必要があります。

      Sが内部ローカル・クラスである場合、Oは、Sの、直接包含するクラスまたはインタフェースの宣言であるとします。

      Sが内部メンバー・クラスである場合、Oは、SがメンバーであるCの、最も内側の、包含するクラスであるとします。

      On番目の語彙的なCの包含クラスまたはインタフェース宣言になるように、nを整数(n 1)にします。

      Sに関してiを直接包含するインスタンスは、thisn番目の字句的な包含インスタンスです。

      継承によってSCのメンバーである場合もありますが、thisのゼロ番目の字句的な包含インスタンス(つまり、this自体)が、Sに関してiを直接包含するインスタンスとして使用されることはありません。

    • そうでない場合、スーパークラス・コンストラクタ呼出しが修飾されていれば、".super"、pの直前にあるPrimary式またはExpressionNameが評価されます。

      pnullと評価されると、NullPointerExceptionが発生し、スーパークラス・コンストラクタ呼出しは突然完了します。

      それ以外の場合は、この評価の結果が、Sに関してiを直接包含するインスタンスです。

  2. Sに関わるiの直接包含インスタンスが特定された後(存在する場合)、スーパークラス・コンストラクタ呼出し文の評価では、通常のメソッド呼出しと同様に、コンストラクタの引数が左から右に評価され、続いてコンストラクタが呼び出されます。

  3. 最後に、スーパークラス・コンストラクタ呼出し文が正常に完了すると、Cのすべてのインスタンス変数イニシャライザ、およびCのすべてのインスタンス・イニシャライザが実行されます。インスタンス・イニシャライザまたはインスタンス変数イニシャライザIのテキストが別のインスタンス・イニシャライザまたはインスタンス変数イニシャライザJの前にある場合、IJより先に実行されます。

    スーパークラス・コンストラクタ呼出しが実際に明示的コンストラクタ呼出し文として出現するか、暗黙的に提供されるかに関係なく、インスタンス変数イニシャライザおよびインスタンス・イニシャライザの実行は実施されます。(代替コンストラクタ呼出しでは、こうした追加の暗黙的な実行は実施されません。)

編集部: スーパークラス・コンストラクタ呼出しの評価には、包含インスタンスの決定を含めないでください。これは、コンパイル時に明確に行われます。

  1. まず、スーパークラス・コンストラクタ呼出しが修飾されている場合は、修飾するプライマリ式が評価されます。修飾プライマリ式がnullと評価されると、NullPointerExceptionが発生し、スーパークラス・コンストラクタ呼出しは突然完了します。修飾された式が突然完了する場合、スーパークラス・コンストラクタ呼出しは同じ理由で突然完了します。

  2. コンストラクタ呼出しの実際の引数は、通常のメソッド呼出しと同様に左から右に評価され、コンストラクタが呼び出されます。

    コンストラクタ呼出しの実際の最初の引数は、包含インスタンスである可能性があることに注意してください。

  3. 最後に、スーパークラス・コンストラクタ呼出しが正常に完了すると、Cのすべてのインスタンス変数イニシャライザとCのすべてのインスタンス・イニシャライザが実行されます。インスタンス・イニシャライザまたはインスタンス変数イニシャライザIのテキストが別のインスタンス・イニシャライザまたはインスタンス変数イニシャライザJの前にある場合、IJより先に実行されます。

    スーパークラス・コンストラクタ呼出しが実際に明示的なコンストラクタ呼出しとして出現するか、暗黙的に提供されるかに関係なく、インスタンス変数イニシャライザおよびインスタンス・イニシャライザは実行されます。(代替コンストラクタ呼出しでは、こうした追加の暗黙的な実行は実施されません。)

例8.8.7.1-1.明示的コンストラクタ呼出し文呼出しに関する制限事項

8.8.7の例にあるColoredPointという最初のコンストラクタが次のように変更されたとします。

class Point {
    int x, y;
    Point(int x, int y) { this.x = x; this.y = y; }
}
class ColoredPoint extends Point {
    static final int WHITE = 0, BLACK = 1;
    int color;
    ColoredPoint(int x, int y) {
        this(x, y, color);  // Changed to color from WHITE
    }
    ColoredPoint(int x, int y, int color) {
        super(x, y);
        this.color = color;
    }
}

この場合、インスタンス変数colorを明示的コンストラクタ呼出しで使用できないため、コンパイル時にエラーが発生します。

例8.8.7.1-2.修飾されたスーパークラス・コンストラクタ呼出し

次のコードでは、ChildOfInnerに語彙的な包含クラスまたはインタフェース宣言がないため、ChildOfInnerのインスタンスに包含インスタンスはありません。ただし、ChildOfInnerのスーパークラス(Inner)には語彙的な包含クラス宣言(Outer)があるため、InnerのインスタンスにはOuterの包含インスタンスが必要です。Outerの包含インスタンスは、Innerのインスタンスの作成時に設定されます。したがって、暗黙的にInnerのインスタンスである、ChildOfInnerのインスタンスを作成する際に、ChildOfInnerのコンストラクタ内で、修飾されたスーパークラス呼出しを使用してOuterの包含インスタンスを提供する必要があります。Outerのインスタンスは、Innerに関してChildOfInnerを直接包含するインスタンスと呼ばれます。

class Outer {
    class Inner {}
}
class ChildOfInner extends Outer.Inner {
    ChildOfInner() { (new Outer()).super(); }
}

意外に感じられますが、Outerの同じインスタンスが、ChildOfInnerの複数のインスタンスについてInnerに関してChildOfInnerを直接包含するインスタンスとして機能することもあります。ChildOfInnerのこれらのインスタンスは、Outerの同じインスタンスに暗黙的にリンクされます。次のプログラムでは、OuterのインスタンスをChildOfInnerのコンストラクタ(修飾されたスーパークラス・コンストラクタ呼出しでそのインスタンスを使用する)に渡すことによって、このことを実現します。明示的コンストラクタ呼出しのルールでは、その呼出しを含むコンストラクタの仮パラメータを使用することは禁止されていません。

class Outer {
    int secret = 5;
    class Inner {
        int  getSecret()      { return secret; }
        void setSecret(int s) { secret = s; }
    }
}
class ChildOfInner extends Outer.Inner {
    ChildOfInner(Outer x) { x.super(); }
}

public class Test {
    public static void main(String[] args) {
        Outer x = new Outer();
        ChildOfInner a = new ChildOfInner(x);
        ChildOfInner b = new ChildOfInner(x);
        System.out.println(b.getSecret());
        a.setSecret(6);
        System.out.println(b.getSecret());
    }
}

このプログラムでは、次の出力が生成されます。

5
6

その結果、ChildOfInnerの異なるインスタンスへの参照が従来の意味で別名でなくても、そのような参照を通じて、Outerの共通インスタンス内のインスタンス変数の操作を見ることができます。

8.10 レコード・クラス

8.10.4 レコード・コンストラクタ宣言

レコード・クラスでは、そのレコード・コンポーネントが適切に初期化されるようにするために、デフォルトのコンストラクタ(8.8.9)は暗黙的に宣言されません。かわりに、レコード・クラスには、レコード・クラスのすべてのコンポーネント・フィールドを初期化する、明示的または暗黙的に宣言された、標準コンストラクタがあります。

レコード宣言で標準コンストラクタを明示的に宣言する方法としては、次の2つがあります。適切なシグネチャを使用して標準コンストラクタを宣言する方法か(8.10.4.1)、コンパクト・コンストラクタを宣言する方法です(8.10.4.2)。

正規として修飾される標準コンストラクタのシグネチャと、コンパクト・コンストラクタ用に導出されるシグネチャがあるとすると、コンストラクタ・シグネチャのルール(8.8.2)では、正規として修飾される標準コンストラクタコンパクト・コンストラクタの両方がレコード宣言にある場合、コンパイル時にエラーが発生することになります。

どちらにしても、明示的に宣言された標準コンストラクタでは、次のように、少なくともレコード・クラスと同じくらいのアクセスを提供する必要があります。

明示的に宣言された標準コンストラクタは、固定個引数コンストラクタまたは可変個引数コンストラクタ(8.8.1)です。

レコード・クラスRの宣言で標準コンストラクタが明示的に宣言されない場合は、標準コンストラクタrが、次の特性で、Rで暗黙的に宣言されます。

レコード・クラスの導出仮パラメータ・リストは、次のように、レコード・ヘッダー内の各レコード・コンポーネントから仮パラメータを順番に導出することで形成されます。

レコード宣言には、標準コンストラクタではないコンストラクタの宣言が含まれている場合があります。レコード宣言内のすべての非標準コンストラクタの本体には、最初に代替コンストラクタ呼出し(8.8.7.1)が含まれている必要があります。そうでないと、コンパイル時にエラーが発生します。

第11章: 例外

11.2コンパイル時の例外検査

11.2.2 文の例外分析と明示的なコンストラクタ呼出し

スロー式に静的型Eがある、finalまたは事実上finalの例外パラメータではないthrow文(14.18)では、E、またはスロー式でスロー可能な例外クラスをスローできます。

たとえば、文throw new java.io.FileNotFoundException();ではjava.io.FileNotFoundExceptionのみをスローできます。正式には、java.io.FileNotFoundExceptionのサブクラスやスーパークラスを"スローできる"わけではありません。

スロー式がcatchCのfinalまたは事実上finalの例外パラメータであるthrow文では、次の場合に、例外クラスEをスローできます。

try文(14.20)では、次のいずれかの場合に例外クラスEをスローできます。

明示的コンストラクタ呼出し文(8.8.7.1)では、次の場合に例外クラスEをスローできます。

switch文(14.11)では、次のいずれかの場合に例外クラスEをスローできます。

Sに直接含まれている式または文でEをスローできる場合は、他の文のSで例外クラスEをスローできます。

明示的コンストラクタ呼出し(8.8.7.1)では、次の場合に例外クラスEをスローできます。

第12章: 実行

12.5 新しいクラス・インスタンスの作成

クラス・インスタンス作成式(15.9)の評価によってクラスがインスタンス化されると、新しいクラス・インスタンスが明示的に作成されます。

次の状況では、新しいクラス・インスタンスが暗黙的に作成される場合があります。

このようなそれぞれの状況は、クラス・インスタンスの作成プロセスの一部として、指定された引数(ない場合もある)で呼び出される特定のコンストラクタ(8.8)を識別します。

新しいクラス・インスタンスが作成されるときは、常に、クラスで宣言されたすべてのインスタンス変数とクラスの各スーパークラスで宣言されたすべてのインスタンス変数(隠すことができるすべてのインスタンス変数を含む)のための空き領域を確保して、新しいクラス・インスタンスにメモリー領域が割り当てられます(8.3)。

オブジェクトのメモリーを割り当てるための空き領域が不十分である場合、OutOfMemoryErrorによってクラス・インスタンスの作成が突然完了します。それ以外の場合は、スーパークラスで宣言された変数を含め、新しいオブジェクトのすべてのインスタンス変数がデフォルト値(4.12.5)に初期化されます。

新しく作成されたオブジェクトへの参照が結果として返される直前に、指定されたコンストラクタが処理され、次の手順に従って新しいオブジェクトが初期化されます。

  1. コンストラクタの引数を、このコンストラクタ呼出しのために新しく作成されたパラメータ変数に割り当てます。

  2. このコンストラクタが(thisを使用して)同一クラス内の別のコンストラクタの明示的なコンストラクタ呼出し(8.8.7.1)で始まる場合、その引数を評価し、この同じ5つのステップを使用して、コンストラクタの再帰的な呼出しを処理します。コンストラクタの呼出しが突然完了する場合、この手順は同じ理由で突然完了します。そうでない場合は、ステップ5から続行します。

  3. このコンストラクタは、(thisを使用する)同一クラス内の別のコンストラクタの明示的なコンストラクタ呼出しからは始まりません。このコンストラクタがObject以外のクラス用の場合、このコンストラクタは、(superを使用する)スーパークラス・コンストラクタの明示的または暗黙的な呼出しから始まります。これら5つのステップを使用して、スーパークラス・コンストラクタを再帰的に呼び出す引数とプロセスを評価します。コンストラクタの呼出しが突然完了する場合、この手順は同じ理由で突然完了します。それ以外の場合は、ステップ4に進みます。

  4. このクラスのインスタンス・イニシャライザおよびインスタンス変数イニシャライザを実行し、インスタンス変数イニシャライザの値を対応するインスタンス変数に左から右へ順に割り当て、クラスのソース・コードにテキストで表示されるようにします。これらのイニシャライザのいずれかを実行して例外が発生した場合、それ以上のイニシャライザは処理されず、この手順が同じ例外で突然完了します。それ以外の場合は、ステップ5に進みます。

  5. このコンストラクタの残りの本体を実行します。例外が突然完了する場合、この手順は同じ理由で突然完了します。それ以外の場合、この手順は正常に完了します。

  1. コンストラクタの引数を、このコンストラクタ呼出しのために新しく作成されたパラメータ変数に割り当てます。

  2. このコンストラクタに明示的コンストラクタ呼出し(8.8.7.1)が含まれていない場合は、ステップ5から続行します。

  3. コンストラクタ本体のプロローグのBlockStatementsを実行します(存在する場合)。文の実行が突然完了した場合は、同じ理由でコンストラクタの実行が突然完了します。そうでない場合は、次のステップに進みます。

  4. 明示的コンストラクタ呼出しは、同じクラス内の別のコンストラクタの呼出し(thisを使用)、またはスーパークラス・コンストラクタの呼出し(superを使用)のどちらかです。コンストラクタ呼出しの引数を評価し、同じ7つのステップを使用してコンストラクタ呼出しを再帰的に処理します。コンストラクタ呼出しが突然完了した場合は、同じ理由でこの手順が突然完了します。そうでない場合は、呼出しが同じクラス内の別のコンストラクタの呼出しであればステップ7から続行し、スーパークラス・コンストラクタの呼出しであればステップ6から続行します。

  5. このコンストラクタがObject以外のクラス用である場合、このコンストラクタには、引数なしで、スーパークラス・コンストラクタの暗黙的な呼出しが含まれています。この場合は、これらの同じ7つのステップを使用して暗黙的コンストラクタ呼出しを再帰的に処理します。コンストラクタ呼出しが突然完了した場合は、同じ理由この手順が突然完了します。そうでない場合は、次のステップに進みます。

  6. このクラスのインスタンス・イニシャライザおよびインスタンス変数イニシャライザを実行し、インスタンス変数イニシャライザの値を対応するインスタンス変数に左から右へ順に割り当て、クラスのソース・コードにテキストで表示されるようにします。これらのイニシャライザのいずれかの実行で例外が発生した場合は、それ以降のイニシャライザは処理されず、同じ例外でこの手順が突然完了します。そうでない場合は、次のステップに進みます。

  7. このコンストラクタのエピローグのBlockStatementsを実行します(存在する場合)。文の実行が突然完了した場合は、同じ理由でこの手順が突然完了します。それ以外の場合、この手順は正常に完了します。

C++とは異なり、Javaプログラミング言語では、新しいクラス・インスタンスの作成中にメソッド・ディスパッチに対するルール変更は指定されません。オブジェクトの初期化中、サブクラスでオーバーライドされるメソッドが呼び出された場合、新しいオブジェクトが完全に初期化される前でも、これらのオーバーライド・メソッドが使用されます。クラスは、コンストラクタ本体のプロローグでフィールドに代入することで、初期化されていない状態の不要な露出を回避できます。

例12.5-1.インスタンス作成の評価

class Point {
    int x, y;
    Point() { x = 1; y = 1; }
}
class ColoredPoint extends Point {
    int color = 0xFF00FF;
}
class Test {
    public static void main(String[] args) {
        ColoredPoint cp = new ColoredPoint();
        System.out.println(cp.color);
    }
}

ここでは、ColoredPointの新しいインスタンスが作成されます。最初に、フィールドxyおよびcolorを保持するために、新しいColoredPointに領域が割り当てられます。これらのフィールドはすべてデフォルト値(この場合は、各フィールドが0)に初期化されます。次に、引数のないColoredPointコンストラクタが最初に呼び出されます。ColoredPointはコンストラクタを宣言しないため、次の形式のデフォルトのコンストラクタが暗黙的に宣言されます。

ColoredPoint() { super(); }

このコンストラクタは、引数なしでPointコンストラクタを呼び出します。Pointコンストラクタはコンストラクタの呼出しで始まらないため、Javaコンパイラでは、引数なしのスーパークラス・コンストラクタの暗黙的な呼出しが次のような記述で指定されます。

Point() { super(); x = 1; y = 1; }

したがって、引数をとらないObjectのコンストラクタが呼び出されます。

クラスObjectにはスーパークラスがないため、再帰がここで終了します。次に、Objectのインスタンス・イニシャライザおよびインスタンス変数イニシャライザが呼び出されます。次に、引数をとらないObjectのコンストラクタの本体が実行されます。このようなコンストラクタはObjectで宣言されないため、Javaコンパイラは、このような特殊なケースでは次のようなデフォルトのコンストラクタを指定します。

Object() { }

このコンストラクタの実行は効果がないまま返されます。

次に、クラスPointのインスタンス変数のすべてのイニシャライザが実行されます。この場合、xおよびyの宣言では初期化式を指定しないため、この例のステップでは何のアクションも必要ありません。その後、Pointコンストラクタの本体が実行され、x1に、y1に設定されます。

次に、クラスColoredPointのインスタンス変数のイニシャライザが実行されます。このステップでは、値0xFF00FFcolorに割り当てます。最後に、ColoredPointコンストラクタの本体の残りの部分エピローグ(superの呼出し後の部分)が実行されます。本体の残りの部分エピローグには文がないため、それ以降のアクションは必要なく、初期化が完了します。

例12.5-2.インスタンス作成時の動的ディスパッチ

class Super {
    Super() { printThree(); }
    void printThree() { System.out.println("three"); }
}
class Test extends Super {
    int three = (int)Math.PI;  // That is, 3
    void printThree() { System.out.println(three); }

    public static void main(String[] args) {
        Test t = new Test();
        t.printThree();
    }
}

このプログラムでは、次の出力が生成されます。

0
3

これは、クラスSuperのコンストラクタでのprintThreeの呼出しによって、クラスSuperprintThreeの定義が呼び出されるのではなく、クラスTestprintThreeのオーバーライド定義が呼び出されることを示しています。したがって、このメソッドは、Testのフィールド・イニシャライザが実行される前に実行されます。このため、最初の値の出力は0、つまりTestのフィールドthreeが初期化されるデフォルト値になります。その後、メソッドmainprintThreeを呼び出すと、printThreeの同じ定義が呼び出されますが、その時点でインスタンス変数threeのイニシャライザが実行されているため、値3が出力されます。

例12.5-3.プロローグでのフィールドの初期化

class Super {
    Super() { printThree(); }
    void printThree() { System.out.println("three"); }
}
class Test extends Super {
    int three;

    public Test() {
        three = (int)Math.PI;  // That is, 3
        super();
    }

    void printThree() { System.out.println(three); }

    public static void main(String[] args) {
        Test t = new Test();
        t.printThree();
    }
}

この例12.5-2の代替は、次の出力を生成します:

3
3

フィールドthreeは、Testクラスのコンストラクタのプロローグで初期化されるため、その代入は、ステップ4のSuperクラスのコンストラクタ本体を評価する前に、オブジェクト初期化プロシージャのステップ3で行われます。この方法でthreeを初期化すると、それはデフォルト値0によって監視できなくなります。

第14章: ブロックと文およびパターン

14.17 return

return文は、メソッド(8.415.12)またはコンストラクタ(8.815.9)の起動元に制御を戻します。

ReturnStatement:
return [Expression] ;

return文には、次の2つの種類があります:

return文は、最も内側で包含しているコンストラクタ、メソッドまたはラムダ式の起動元に制御を転送しようとします。この包含している宣言または式は、returnターゲットと呼ばれます。値がreturn文の場合は、そのの値が呼出しの値になります。

return文にreturnターゲットがない場合は、コンパイル時にエラーが発生します。

returnターゲットに(i) return文を囲むインスタンスまたは静的イニシャライザ、または(ii) return文を囲むswitch式のいずれかが含まれている場合は、コンパイル時にエラーが発生します。

値が指定されていないreturn文のreturnターゲットがメソッドで、そのメソッドがvoidとして宣言されていない場合は、コンパイル時にエラーが発生します。

return文のreturnターゲットがコンストラクタのときに、このコンストラクタのプロローグ(8.8.7)にreturn文があると、コンパイル時にエラーが発生します。

値がreturn文のreturnターゲットがコンストラクタまたはvoidと宣言されたメソッドの場合は、コンパイル時にエラーが発生します。

値がreturn文のreturnターゲットが、戻り値の型がTと宣言されたメソッドで、の型にTとの割当て可能性の互換性がない場合(5.2)は、コンパイル時にエラーが発生します。

値を指定しないreturn文の実行は、値のないreturnが存在するために常に突然完了します。

値がreturn文を実行すると、そのが最初に評価されます。の評価がなんらかの理由で突然完了する場合、その理由によってreturn文は突然完了します。の評価が正常に完了し、値Vが生成されると、値がVのreturnが存在するためにreturn文は突然完了します。

そのことから、return文が常に突然完了することがわかります。

前述の説明では、単に「制御を転送する」ではなく「制御を転送しようとする」と表現しています。これは、tryブロックまたはcatch句にreturn文が含まれているメソッドまたはコンストラクタ内にtry文(14.20)がある場合、該当するtry文のfinally句は、起動元のメソッドまたはコンストラクタに制御が転送される前に、最も内側から最も外側の順に実行されます。finally句が突然完了すると、return文によって開始された制御の転送が中断されることがあります。

14.22 到達不可能な文

到達不可能であるために文を実行できない場合は、コンパイル時にエラーが発生します。

この項では、"到達可能"という言葉を正確に説明します。それは、文を含むコンストラクタ、メソッド、インスタンス・イニシャライザまたは静的イニシャライザの先頭からその文自体までの実行パスが存在する必要があるという意味です。分析では文の構造が考慮されます。条件式に定数値trueがあるwhiledoおよびfor文の特別な処理を除き、式の値は、フロー分析では考慮されません。

たとえば、Javaコンパイラでは、次のコードが受け入れられます。

{
    int n = 5;
    while (n > 7) k = 2;
}

コンパイル時にnの値がわかっている場合でも、原則として、コンパイル時に、kへの代入を実行できないとわかることがあります。

この項で示すルールでは、次の2つの技術用語の意味を明らかにします。

ルールでは、文は、それに到達可能である場合のみ、正常完了可能です。

さらに2つの技術用語を使用します。

この規則は次のとおりです。

編集部: 14.22項の残りの部分に変更はありません。

第15章: 式

15.8 1次式

15.8.3 this

キーワードthisは、次のコンテキストで式として使用できます。

式として使用したときは、キーワードthisでは、インスタンス・メソッドを呼び出したオブジェクト(15.12)または構成されるオブジェクトへの参照である値が示されます。ラムダ本体(15.27.2)においてthisで示される値は、その周囲のコンテキストにおいてthisで示される値と同じです。

キーワードthisは、明示的コンストラクタ呼出し文呼出し(8.8.7.1)でも使用され、メソッドまたはコンストラクタのレシーバ・パラメータを示すためにも使用されます(8.4)。

静的コンテキストにthis式が出現すると、コンパイル時にエラーが発生します(8.1.3)。

Cは、this式の、最も内側の、包含するクラスまたはインタフェースの宣言であるとします。

this式がCの早期構築コンテキスト(8.8.7)に出現した場合、コンパイル時にエラーが発生します。ただし、単純な代入式(15.26)の左側のオペランドとして指定される、フィールド・アクセス式(15.11)の修飾子として出現し、単純な代入式がCの早期構築コンテキストに含まれるラムダ式または内部クラス宣言に含まれていない場合を除きます。

Cが汎用であり、型パラメータがF1,...,Fnである場合、thisの型はC<F1,...,Fn>です。そうでない場合、thisの型はCです。

実行時に、参照される実際のオブジェクトのクラスは、C、またはCのサブクラス(8.1.5)である場合があります。

例15.8.3-1.this

class IntVector {
    int[] v;
    boolean equals(IntVector other) {
        if (this == other)
            return true;
        if (v.length != other.v.length)
            return false;
        for (int i = 0; i < v.length; i++) {
            if (v[i] != other.v[i]) return false;
        }
        return true;
    }
}

ここでは、クラスIntVectorは2つのベクターを比較するメソッドequalsを実装します。一方のベクターが、equalsメソッドが呼び出されたものと同じベクター・オブジェクトの場合、長さと値の比較チェックをスキップできます。equalsメソッドでは、もう一方のオブジェクトへの参照をthisと比較してこのチェックを実装します。

15.8.4 修飾されたthis

字句的な包含インスタンス(8.1.3)は、キーワードthisを明示的に修飾することによって参照できます。

nは整数であり、TypeNameで、修飾されたthis式を宣言で直接包含するクラスまたはインタフェースの、n番目の、字句として包含するクラスまたはインタフェースの宣言が示されているとします。

修飾されたthis式のTypeName.thisの値は、thisの、n番目の、字句として包含するインスタンスです。

TypeNameで汎用クラスが示されおり、型パラメータがF1,...,Fnである場合、修飾されたthis式の型は、TypeName<F1,...,Fn>です。そうでない場合、修飾されたthis式の型は、TypeNameです。

修飾されたthis式が静的コンテキストに出現すると、コンパイル時にエラーが発生します(8.1.3)。

修飾されたthis式がTypeNameで指定されたクラスの早期構築コンテキスト(8.8.7)に出現した場合、コンパイル時にエラーが発生します。ただし、単純な代入式(15.26)の左側のオペランドとして指定される、フィールド・アクセス式(15.11)の修飾子として出現し、単純な代入式がTypeNameで指定されたクラスの早期構築コンテキストに含まれるラムダ式または内部クラス宣言に含まれていない場合を除きます。

修飾されたthis式を宣言で直接包含するクラスまたはインタフェースが、TypeNameの内部クラスまたはTypeName自体でない場合は、コンパイル時にエラーが発生します。

15.9 クラス・インスタンス作成式

15.9.2 包含インスタンスの特定

Cはインスタンス化されるクラス、iは作成されるインスタンスであるとします。Cが内部クラスである場合、iには、直接包含するインスタンス(8.1.3)がある可能性があり、次のように特定されます。

Cが無名クラスで、かつその直接スーパークラスSが内部クラスである場合、iは、Sに関して直接包含するインスタンスを持つ可能性があり、次のように判別されます。

15.11 フィールド・アクセス式

15.11.2 superを使用したスーパークラス・メンバーへのアクセス

super.Identifierの形式は、現在のオブジェクトのIdentifierというフィールドを参照していますが、その現在のオブジェクトは、現在のクラスのスーパークラスのインスタンスとして表示されます。

T.super.Identifierという形式は、Tに対応する語彙的な包含インスタンスのIdentifierというフィールドを参照しますが、そのインスタンスはTのスーパークラスのインスタンスとして表示されます。

キーワードsuperを使用する形式は、キーワードthisを式(15.8.3)として使用できるクラス宣言内の場所で使用できます。

キーワードsuperを使用するフィールド・アクセス式が静的コンテキスト(8.1.3)または現在のクラスの早期構築コンテキスト(8.8.7)出現出現する場合は、コンパイル時にエラーが発生します。

super.Identifierという形式のフィールド・アクセス式の場合:

T.super.Identifierという形式のフィールド・アクセス式の場合:

フィールド・アクセス式super.fがクラスCに出現し、Cの直接スーパークラスがSであるとします。S内のfにクラスCからアクセスできる場合(6.6)、super.fはクラスS本体内の式this.fであったものとして扱われます。そうでない場合、コンパイル時にエラーが発生します。

このため、super.fはクラスS内でアクセス可能なフィールドfがクラスC内のフィールドfの宣言で非表示にされていても、そのフィールドにアクセスできます。

フィールド・アクセス式T.super.fがクラスC内に出現し、Tが示すクラスの直接スーパークラスが完全修飾名Sのクラスであるとします。S内のfCからアクセス可能な場合、T.super.fはクラスSの本体内のthis.f式であったものとして扱われます。そうでない場合、コンパイル時にエラーが発生します。

このため、T.super.fは、クラスS内でアクセス可能なフィールドfがクラスT内のフィールドfの宣言によって非表示にされていても、そのフィールドにアクセスできます。

例15.11.2-1.super

interface I           { int x = 0; }
class T1 implements I { int x = 1; }
class T2 extends T1   { int x = 2; }
class T3 extends T2 {
    int x = 3;
    void test() {
        System.out.println("x=\t\t"          + x);
        System.out.println("super.x=\t\t"    + super.x);
        System.out.println("((T2)this).x=\t" + ((T2)this).x);
        System.out.println("((T1)this).x=\t" + ((T1)this).x);
        System.out.println("((I)this).x=\t"  + ((I)this).x);
    }
}
class Test {
    public static void main(String[] args) {
        new T3().test();
    }
}

このプログラムでは、次の出力が生成されます。

x=              3
super.x=        2
((T2)this).x=   2
((T1)this).x=   1
((I)this).x=    0

クラスT3内では、式super.xxにパッケージ・アクセスがあれば、実質的に((T2)this).xと同じです。super.xはスーパークラスのprotectedメンバーへのアクセスが困難なため、キャストについては指定されていません。

15.12 メソッド呼出し式

15.12.3 コンパイル時のステップ3: 選択したメソッドが適切かどうか

メソッド呼出しについて最も的確なメソッド宣言がある場合、メソッド呼出しのコンパイル時宣言と呼ばれます。

メソッド呼出しの引数が、コンパイル時宣言の呼出しタイプから導出されたそのターゲット型と互換性がない場合は、コンパイル時にエラーが発生します。

コンパイル時宣言が可変引数呼出しによって適用可能である場合、メソッドの呼出しタイプの最後の仮パラメータ型がFn[]であるとすると、Fnのイレイジャである型が呼出しの時点でアクセス可能でない場合(6.6)、コンパイル時にエラーが発生します。

コンパイル時宣言がvoidである場合、メソッド呼出しは、最上位式(つまり、式の文にあるか、for文のForInitまたはForUpdate部分にあるExpression)である必要があります。そうでない場合はコンパイル時にエラーが発生します。このようなメソッド呼出しは値を生成しないため、値が不要な状況でのみ使用する必要があります。

さらに、コンパイル時宣言が適切であるかどうかは、次のように、左カッコの前にあるメソッド呼出し式の形式によって異なることがあります。

compile-timeパラメータ型compile-time結果は次のように決定されます。

メソッドは、次がすべてtrueの場合にシグネチャ多相になります。

次のコンパイル時の情報は、実行時に使用するメソッド呼出しに関連するものです。

コンパイル時宣言の呼出し型の結果がvoidの場合、メソッド呼出し式の型は、コンパイル時宣言の呼出し型の戻り型へのキャプチャ変換を適用して取得します(5.1.10)。

15.13 メソッド参照式

呼出しを実際に実行することなく、メソッドの呼出しを参照するために、メソッド参照式が使用されます。特定の形式のメソッド参照式でも、メソッド呼出しと同様に、クラス・インスタンスの作成(15.9)または配列の作成(15.10)を処理できます。

MethodReference:
ExpressionName :: [TypeArguments] Identifier
Primary :: [TypeArguments] Identifier
ReferenceType :: [TypeArguments] Identifier
super :: [TypeArguments] Identifier
TypeName . super :: [TypeArguments] Identifier
ClassType :: [TypeArguments] new
ArrayType :: new

::の右側にTypeArgumentsが存在する場合、いずれかの型引数がワイルドカード(4.5.1)であると、コンパイル時にエラーが発生します。

メソッド参照式の形式がExpressionName :: [TypeArguments] IdentifierまたはPrimary :: [TypeArguments] Identifierの場合、ExpressionNameまたはPrimaryの型が参照型ではないと、コンパイル時にエラーが発生します。

メソッド参照式の形式がsuper :: [TypeArguments] Identifierである場合、Eは、メソッド参照式を直接包含するクラス宣言またはインタフェース宣言であるとします。EがクラスObjectであるかEがインタフェースである場合は、コンパイル時にエラーが発生します。

メソッド参照式の形式がTypeName . super :: [TypeArguments] Identifierの場合、次のようになります。

メソッド参照式の形式がsuper :: [TypeArguments] IdentifierまたはTypeName . super :: [TypeArguments] Identifierの場合は、その式が静的コンテキスト(8.1.3)または現在のクラスの早期構築コンテキスト(8.8.7)に示されると、コンパイル時にエラーが発生します。

メソッド参照式の形式がClassType :: [TypeArguments] newの場合、次のようになります。

メソッド参照式の形式がArrayType :: newの場合、ArrayTypeは、具象化可能(4.7)である型を示している必要があります。そうでない場合、コンパイル時にエラーが発生します。

インスタンス・メソッドのターゲット参照(15.12.4.1)は、ExpressionNamePrimaryまたはsuperを使用してメソッド参照式によって提供するか、後でそのメソッドを呼び出すときに提供することができます。新しい内部クラス・インスタンス(15.9.2)の直接包含インスタンスは、thisの、字句として包含するインスタンス(8.1.3)によって提供されます。

型の複数のメンバー・メソッドに同じ名前が付いている場合、またはクラスに複数のコンストラクタがある場合は、15.13.1で示されているように、メソッド参照式によってターゲット指定された関数インタフェース型に基づいて適切なメソッドまたはコンストラクタが選択されます。

メソッドまたはコンストラクタが汎用である場合は、適切な型引数を推測するか明示的に指定することができます。同様に、メソッド参照式で示される汎用型の型引数は、明示的に指定するか推測することができます。

メソッド参照式は、必ず多重式です(15.2)。

プログラムで代入コンテキスト(5.2)、呼出しコンテキスト(5.3)またはキャスト・コンテキスト(5.5)以外の場所でメソッド参照式が出現する場合は、コンパイル時にエラーが発生します。

メソッド参照式を評価すると、関数インタフェース型のインスタンスが生成されます(9.8)。これにより、対応するメソッドの実行は発生しません。そうではなく、後で関数インタフェースの適切なメソッドが呼び出されたときにその実行が発生する場合があります。

いくつかのメソッド参照式を次に示します。まずはターゲット参照なし、次にターゲット参照ありです。

String::length             // instance method
System::currentTimeMillis  // static method
List<String>::size  // explicit type arguments for generic type
List::size          // inferred type arguments for generic type
int[]::clone
T::tvarMember

System.out::println
"abc"::length
foo[x]::bar
(test ? list.replaceAll(String::trim) : list) :: iterator
super::toString

さらにいくつかのメソッド参照式を次に示します。

String::valueOf       // overload resolution needed
Arrays::sort          // type arguments inferred from context
Arrays::<String>sort  // explicit type arguments

オブジェクトまたは配列が遅れて作成されるメソッド参照式を次に示します。

ArrayList<String>::new     // constructor for parameterized type
ArrayList::new             // inferred type arguments
                           // for generic class
Foo::<Integer>new          // explicit type arguments
                           // for generic constructor
Bar<String>::<Integer>new  // generic class, generic constructor
Outer.Inner::new           // inner class constructor
int[]::new                 // array creation

照合する特定の署名(Arrays::sort(int[])など)を指定することはできません。かわりに、関数インタフェースでは、オーバーロード解決アルゴリズム(15.12.2)への入力として使用される引数タイプが用意されています。これで大部分のユースケースに対応できます。まれですが、より細かい制御が必要になった場合は、ラムダ式を使用できます。

デリミタ(List<String>::size)の前にクラス名で型引数構文を使用すると、解析中に、型引数のカッコとしての<とより小演算子としての<の区別に関する問題が発生します。理論的には、これはキャスト式で型引数を許可するより悪いことではありません。ただし、その違いは、キャストのケースは(トークンが検出されたときのみ発生するという点です。メソッド参照式を追加すると、すべての式の先頭が、パラメータ化された型になる可能性があります。

15.13.1 メソッド参照のコンパイル時宣言

メソッド参照式のコンパイル時宣言は、式の参照先のメソッドです。特殊なケースでは、コンパイル時宣言は実際に存在しませんが、クラス・インスタンス作成または配列作成を表す概念上のメソッドが存在します。コンパイル時宣言の選択は、メソッド呼出しのコンパイル時宣言が呼出しの引数に依存するように、式がターゲットとする関数型に依存します(15.12.3)。

コンパイル時宣言の検索では、15.12.1および15.12.2のメソッド呼出しのプロセスを次のようにミラー化します。

メソッド参照式の形式がReferenceType :: [TypeArguments] Identifierであり、コンパイル時宣言がstaticReferenceTypeが単純名または修飾名ではない場合(6.2)、コンパイル時にエラーが発生します。

メソッド参照式の形式がsuper :: [TypeArguments] IdentifierまたはTypeName . super :: [TypeArguments] Identifierで、コンパイル時宣言がabstractの場合、コンパイル時にエラーが発生します。

メソッド参照式の形式がsuper :: [TypeArguments] IdentifierまたはTypeName . super :: [TypeArguments] Identifierの場合は、メソッド参照式が静的コンテキスト(8.1.3)または現在のクラスの早期構築コンテキスト(8.8.7)に示されると、コンパイル時にエラーが発生します。

メソッド参照式の形式がTypeName . super :: [TypeArguments] Identifierであり、TypeNameでクラスCが示されており、メソッド参照式の、直接包含するクラスまたはインタフェースの宣言がC、またはCの内部クラスでない場合は、コンパイル時にエラーが発生します。

メソッド参照式の形式がTypeName . super :: [TypeArguments] Identifierであり、TypeNameでインタフェースが示されており、メソッドがコンパイル時宣言とは別に存在し、それによって、メソッド宣言式(8.4.8, 9.4.1)を直接含む宣言があるクラスまたはインタフェースの直接スーパークラスまたは直接スーパーインタフェースからのコンパイル時宣言がオーバーライドされる場合は、コンパイル時にエラーが発生します。

メソッド参照式の形式がClassType :: [TypeArguments] newであり、15.9.2の規定のとおり、ClassTypeの包含インスタンスを決定する際にコンパイル時エラーが発生した場合、コンパイル時にエラーが発生します(メソッド参照式は未修飾クラス・インスタンス作成式として扱われます)。

形式ReferenceType :: [TypeArguments] Identifierのメソッド参照式は、複数の方法で解釈できます。Identifierがインスタンス・メソッドを参照している場合、Identifierstaticメソッドを参照する場合より、暗黙的ラムダ式のパラメータが多くなります。それぞれのケースのパラメータ型は異なるため、ReferenceTypeに適用可能な両方のメソッドを指定すると、それぞれが前述の検索アルゴリズムによって個別に認識されます。

あいまいさの例は次のとおりです。

interface Fun<T,R> { R apply(T arg); }

class C {
    int size() { return 0; }
    static int size(Object arg) { return 0; }

    void test() {
        Fun<C, Integer> f1 = C::size;
          // Error: instance method size()
          // or static method size(Object)?
    }
}

適用可能なstaticメソッドより的確な適用可能インスタンス・メソッドを指定しても、このあいまいさは解決できません。

interface Fun<T,R> { R apply(T arg); }

class C {
    int size() { return 0; }
    static int size(Object arg) { return 0; }
    int size(C arg) { return 0; }

    void test() {
        Fun<C, Integer> f1 = C::size;
          // Error: instance method size()
          // or static method size(Object)?
    }
}

検索はスマートに行われるためにこのようなあいまいさは無視され、(両方の検索から生成された)適用可能なメソッドがすべてインスタンス・メソッドになります。

interface Fun<T,R> { R apply(T arg); }

class C {
    int size() { return 0; }
    int size(Object arg) { return 0; }
    int size(C arg) { return 0; }

    void test() {
        Fun<C, Integer> f1 = C::size;
          // OK: reference is to instance method size()
    }
}

便宜上、汎用型の名前をインスタンス・メソッドの参照に使用する場合(レシーバが最初のパラメータになる場合)ターゲット型が型引数の決定に使用されます。これによって、Pair<String,Integer>::firstのかわりにPair::firstを使用するなどの用途が促進されます。同様に、Pair::newなどのメソッド参照は「ダイヤモンド」インスタンス作成(new Pair<>())として扱われます。「ダイヤモンド」は暗黙的のため、この形式はRAW型をインスタンス化しません。事実、RAW型のコンストラクタへの参照を表現する方法はありません。

一部のメソッド参照式では、ターゲットの関数型に関係なく、使用できるコンパイル時宣言は1つのみであり、その呼出し型も1つのみです(15.12.2.6)。このようなメソッド参照式はexactと呼ばれます。exactではないメソッド参照式はinexactと呼ばれます。

次をすべて満たす場合、Identifierで終了するメソッド参照式はexactです。

次をすべて満たす場合、形式ClassType :: [TypeArguments] newのメソッド参照式はexactです。

形式ArrayType :: newのメソッド参照式は常にexactです。

第16章: 明確な割当て

文で宣言されたすべてのローカル変数(14.4.214.14.114.14.214.20.3)と、すべての空のfinalフィールド(4.12.48.3.1.2)は、その値のアクセス時に確実に割り当てられた値を持っている必要があります。

その値へのアクセスは、式内で単純割当て演算子= (15.26.1)の左側のオペランド以外の場所に出現する変数の単純名(つまり、フィールドの場合はthisで修飾されたフィールドの単純名)で構成されます。

xで宣言されたローカル変数または空のfinalフィールドxのアクセスでは、そのアクセスの前にxが確実に割り当てられている必要があります。そうしていないと、コンパイル時にエラーが発生します。

同様に、すべての空のfinal変数は最大で1回割り当てる必要があります。割当てが出現したときには、明確に割当てが解除されている必要があります。

このような割当ては、変数の単純名(つまり、フィールドの場合はthisで修飾されたその単純名)が割当て演算子の左側に出現する場合にのみ出現するように定義されます。

空のfinal変数へのすべての割当てについて、割当ての前に変数を明確に割当て解除する必要があります。そうしないと、コンパイル時にエラーが発生します。

同様に、クラスCのコンストラクタ内に現れる代替コンストラクタ呼出し(8.8.7.1)ごとに、Cで宣言したCの空白のfinalインスタンス変数は、代替コンストラクタ呼出しの引数リストの後に確実に割当て解除する必要があります。そうしていないと、コンパイル時にエラーが発生します。

パターン(14.30)で宣言されたローカル変数は、明確な割当てのルールの対象になりません。パターンで宣言されたすべてのローカル変数は、パターン・マッチングのプロセスで初期化されるため、アクセス時には常に値を持っています。

この章の残りの部分では、「前に明確に割り当てる」および「前に明確に割当て解除する」という語句について正確に説明します。

...

16.2 明確な割当ておよび文

16.2.2 ブロック

編集部: コンストラクタの本体は、特にプロローグ・セクションとエピローグ・セクションに分けられるようになった現在では、適切にブロックと見なすことはできません。コンストラクタ本体の処理については、16.9で説明することになりました。

Vは、次の場合にブロックB内のどこでも確実に割当て解除されます:

これらの条件は直感に反するもので、説明が必要です。単純な代入V = eについて考えてみます。Veの後に確実に割り当てられる場合は、次のいずれかになります:

そのため、コンパイル時にエラーが発生しないプログラムによって条件が満たされていると、BVへの割当ては実行時に実際には行われないと結論付けられます。

16.9 明確な割当て、コンストラクタおよびインスタンス・イニシャライザ

CVのスコープ内で宣言されたクラスとします。その場合:

Cをクラスとし、VCで宣言されたCの空のfinalstaticメンバー・フィールドとします。その場合:

Cをクラスとし、VCのスーパークラスで宣言されたCの空のfinalstaticメンバー・フィールドとします。その場合:

Cをクラスとし、VCの空のfinal staticメンバー・フィールドとします。その場合:

Cをクラスとし、VCのコンストラクタまたはインスタンス変数イニシャライザに含まれた文Sで宣言されたローカル変数とします。その場合:

クラスCのコンストラクタ(8.8.7)内では、次のルールが適用されます:

Cをクラスとし、VCのスーパークラスで宣言されたCの空のfinalメンバー・フィールドとします。その場合: