web-dev-qa-db-ja.com

2つのプログラムに前方参照エラーがあるのに、3番目のプログラムにはないのはなぜですか?

以下はコンパイルされず、「不正な前方参照」メッセージが表示されます。

class StaticInitialisation {

    static
    {
        System.out.println("Test string is: " + testString);
    }

    private static String testString;

    public static void main(String args[]) {
        new StaticInitialisation();
    }
}

ただし、以下はコンパイルされます。

class InstanceInitialisation1 {

    {
        System.out.println("Test string is: " + this.testString);
    }

    private String testString;

    public static void main(String args[]) {
        new InstanceInitialisation1();
    }
}

ただし、以下はコンパイルされず、「不正な前方参照」メッセージが表示されます。

class InstanceInitialisation2 {

        private String testString1;

    {
        testString1 = testString2;
    }

    private String testString2;

    public static void main(String args[]) {
        new InstanceInitialisation2();
    }
}

InstanceInitialisation1がコンパイルするのに、StaticInitialisationとInstanceInitialisation2がコンパイルされないのはなぜですか?

54
Martin

これは、JLSのセクション 8.3. でカバーされています。

これらのクラス変数がスコープ内にある場合でも、使用後に宣言がテキストで表示されるクラス変数の使用が制限される場合があります(§6.3)。具体的には、次のすべてが当てはまる場合、コンパイル時エラーになります。

  • クラスまたはインターフェイスCでのクラス変数の宣言は、クラス変数の使用後にテキストで表示されます。

  • 使用法は、Cのクラス変数初期化子またはCの静的初期化子のいずれかでの単純な名前です。

  • 使用は割り当ての左側ではありません。

  • Cは、使用を囲む最も内側のクラスまたはインターフェースです。

インスタンス変数がスコープ内にある場合でも、使用後に宣言がテキストで表示されるインスタンス変数の使用が制限される場合があります。具体的には、次のすべてが当てはまる場合、コンパイル時エラーになります。

  • クラスまたはインターフェイスCでのインスタンス変数の宣言は、インスタンス変数の使用後にテキストで表示されます。

  • 使用法は、Cのインスタンス変数初期化子またはCのインスタンス初期化子のいずれかでの単純な名前です。

  • 使用は割り当ての左側ではありません。

  • Cは、使用を囲む最も内側のクラスまたはインターフェースです。

2番目のケースでは、使用は単純な名前ではありません-明示的にthisを取得します。つまり、上記の2番目のリストの2番目の箇条書きに準拠していないため、エラーは発生しません。

次のように変更した場合:

System.out.println("Test string is: " + testString);

...その後はコンパイルされません。

または、反対方向に、静的初期化ブロックのコードを次のように変更できます。

System.out.println("Test string is: " + StaticInitialisation.testString);

奇妙ですが、それがその方法です。

57
Jon Skeet

単純な理由-すべての前方参照を分析して禁止するのは費用がかかりすぎるか不可能です。例えば.

{
    print( getX();  );    // this.x
    print( that().x );    // this.x
}

int x;
int getX(){ return x; }

This that(){ return this; }

仕様は、一般的なプログラマーの間違いを示すいくつかの単純なケースを禁止することで決着します。

参照 「this」を追加すると再帰的初期化子が機能しますか?

2
ZhongYu

これらの2つの例を見てみましょう。これで、明確になると思います。

public class InstanceAndSataticInit {

    {
        System.out.println("Test string is (instance init): " + this.testString);
    }

    static{
        System.out.println("Test string is (static init ): " + InstanceAndSataticInit.testStringStatic);
    }

    public  static String testStringStatic="test";
    public  String testString="test";

    public static void main(String args[]) {
        new InstanceAndSataticInit();
    }

}

出力:

Test string is (static init ): null
Test string is (instance init): null

そして

public class InstanceAndSataticInitVariableFirst {

    public  static String testStringStatic="test";
    public  String testString="test";

    {
        System.out.println("Test string is (instance init): " + this.testString);
    }

    static{
        System.out.println("Test string is (static init ): " + InstanceAndSataticInitVariableFirst.testStringStatic);
    }



    public static void main(String args[]) {
        new InstanceAndSataticInitVariableFirst();
    }


}

出力:

Test string is (static init ): test
Test string is (instance init): test

つまり、シーケンスは次のようになります。

  1. 静的変数は作成されますが、初期化されません。

  2. 静的初期化は、指定されたシーケンスに従って実行されます。

  3. 非静的変数は作成されますが、初期化されません。
  4. 非静的初期化は、指定されたシーケンスに従って実行されます。

シーケンスとは、コード内の外観を意味します。

この手順はあなたの2つが機能しないStaticInitialisationおよびInstanceInitialisation2に答えると思います

しかし、2番目の作業例InstanceInitialisation1thisキーワードを使用する場合、実際にはコンパイラがテキスト階層を見落とすのを助けています。最初の例でInstanceAndSataticInit.testStringStaticを呼び出すと、staticの場合にも同じことが起こりますInstanceAndSataticInit

2
Saif

ここで理解しなければならないのは、2番目のコードスニペットでブロックとこのキーワードを使用しているということです。

  1. オブジェクトが作成されると、ブロックが実行されます。
  2. これは、オブジェクトがヒープ領域に作成されることを意味します。
  3. 外部でこのキーワードを使用してインスタンス変数の値を取得しています。
  4. ここでは、値として返されるデフォルト値で作成されたオブジェクト。
  5. このキーワードを使用しないと、2番目のスニペットもコンパイルできません。
1