web-dev-qa-db-ja.com

最終的な意味は不明確ですか?

まず、パズル:次のコードは何を出力しますか?

public class RecursiveStatic {
    public static void main(String[] args) {
        System.out.println(scale(5));
    }

    private static final long X = scale(10);

    private static long scale(long value) {
        return X * value;
    }
}

回答:

下のネタバレ。


Xをscale(long)で印刷し、X = scale(10) + 3を再定義すると、印刷結果はX = 0、次にX = 3となります。つまり、Xは一時的に0に設定され、後で3に設定されます。これはfinalの違反です。

静的修飾子は、最終修飾子と組み合わせて、定数の定義にも使用されます。最後の修飾子は、 このフィールドの値は に変更できないことを示します。

ソース: https://docs.Oracle.com/javase/tutorial/Java/javaOO/classvars.html [強調表示]


私の質問:これはバグですか? finalは正しく定義されていますか?


これが私が興味を持っているコードです。Xには2つの異なる値が割り当てられています:03。これはfinalの違反であると思います。

public class RecursiveStatic {
    public static void main(String[] args) {
        System.out.println(scale(5));
    }

    private static final long X = scale(10) + 3;

    private static long scale(long value) {
        System.out.println("X = " + X);
        return X * value;
    }
}

この質問は Javaの静的最終フィールドの初期化順序 の複製の可能性として報告されています。私の質問ではfinalタグと組み合わせた周期的な初期化について説明していますが、他の質問では初期化の順序について説明しているので、この質問は not 重複であると思います。他の質問だけでは、自分の質問のコードがエラーにならないのは理解できません。

これは、ernestoが取得する出力を見ると特に明らかです。afinalでタグ付けされている場合、彼は次の出力を取得します。

a=5
a=5

これは私の質問の主要部分を含まない:final変数はどのようにその変数を変えるのか?

180
Little Helper

ここで決勝戦とは関係ありません。

インスタンスまたはクラスレベルなので、まだ何も割り当てられていない場合はデフォルト値が保持されます。代入せずにアクセスしたときに0が表示されるのはそのためです。

完全に代入しないでXにアクセスすると、デフォルト値のlongのデフォルト値の0が保持されます。

22
Suresh Atta

バグではありません。

scaleへの最初の呼び出しがから呼び出されたとき

private static final long X = scale(10);

return X * valueを評価しようとします。 Xにはまだ値が割り当てられていないため、longのデフォルト値(0)が使用されます。

そのため、そのコード行はX * 10、つまり0 * 10、つまり0と評価されます。

20
OldCurmudgeon

それはまったくバグではありません、単純にそれが 違法な形式ではない 前方参照の/ /他には何もないということです。

String x = y;
String y = "a"; // this will not compile 


String x = getIt(); // this will compile, but will be null
String y = "a";

public String getIt(){
    return y;
}

それは単に仕様によって許可されています。

あなたの例を挙げると、これはまさにこれが一致する場所です:

private static final long X = scale(10) + 3;

前方参照 scaleに対して行っていますが、これは前述のように違法ではありませんが、デフォルト値のXを取得することを可能にします。繰り返しになりますが、これはSpecによって許可されているため(厳密には禁止されていません)、正常に機能します。

14
Eugene

クラスレベルメンバは、クラス定義内のコードで初期化できます。コンパイルされたバイトコードはクラスメンバをインラインで初期化できません。 (インスタンスメンバーは同様に扱われますが、これは提供された質問には関係ありません。)

次のように書くと:

public class Demo1 {
    private static final long DemoLong1 = 1000;
}

生成されたバイトコードは次のようになります。

public class Demo2 {
    private static final long DemoLong2;

    static {
        DemoLong2 = 1000;
    }
}

初期化コードは、クラスローダが最初にクラスをロードするときに実行される静的初期化子内に配置されます。この知識があれば、元のサンプルは次のようになります。

public class RecursiveStatic {
    private static final long X;

    private static long scale(long value) {
        return X * value;
    }

    static {
        X = scale(10);
    }

    public static void main(String[] args) {
        System.out.println(scale(5));
    }
}
  1. JVMはRecursiveStaticをjarのエントリポイントとしてロードします。
  2. クラス定義がロードされると、クラスローダは静的初期化子を実行します。
  3. 初期化子は関数scale(10)を呼び出してstatic finalフィールドにXを割り当てます。
  4. scale(long)関数は、初期化されていないXの値(デフォルトはlongまたは0)を読み取って、クラスが部分的に初期化されている間に実行されます。
  5. 0 * 10の値がXに割り当てられ、クラスローダーが完了します。
  6. JVMは、scale(5)を呼び出すpublic static void mainメソッドを実行します。このメソッドは、初期化された0のX値に5を掛けて0を返します。

静的最終フィールドXは一度だけ割り当てられ、finalキーワードが保持する保証は保持されます。代入に3を追加する後続のクエリでは、上記のステップ5が値0 * 10 + 3である3の評価になり、mainメソッドは値3 * 5である15の結果を出力します。

4
psaxton

オブジェクトの初期化されていないフィールドを読むと、コンパイルエラーになるはずです。 Javaにとっては残念ながら、そうではありません。

私が標準の詳細を知らないけれども、これがそうである根本的な理由がオブジェクトがどのように具体化されそして構築されるかの定義の奥深くに「隠されている」と思う。

ある意味では、finalは、その目的がこの問題によるものであることを達成することすらできないため、定義が明確ではありません。しかし、すべてのクラスが正しく書かれていれば、この問題はありません。つまり、すべてのフィールドは常にすべてのコンストラクタに設定され、そのコンストラクタの1つを呼び出さずにオブジェクトが作成されることはありません。シリアライゼーションライブラリを使用する必要があるまでは、これは当然のことです。

3
Kafein