1. ホーム
  2. java

[解決済み] finalは定義が不明確?

2022-04-30 03:56:25

質問

まず、パズルです。 次のコードは何を表示するのでしょうか?

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

答えてください。

0

以下、ネタバレあり。


印刷する場合 X を scale(long) で再定義し X = scale(10) + 3 , となります。 X = 0 では X = 3 . これは、次のことを意味します。 X が一時的に 0 に設定し、後で 3 . これは final !

static修飾子とfinal修飾子の組み合わせは、定数を定義する場合にも使用されます。 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 static final フィールド初期化順序 . 私は、この質問は ではなく 重複している もう一つの質問は、初期化の順序について述べているのに対して 私の質問は、周期的な初期化についてです。 final タグを使用します。 もう一つの質問だけでは、なぜ私の質問のコードがエラーにならないのか理解できないでしょう。

これは特にernestoが取得する出力を見れば明らかです。 いつ a がタグ付けされている場合 final を実行すると、次のような出力が得られます。

a=5
a=5

というのは、私の質問の本筋とは関係ありません。どのように final はその変数を変更するのですか?

どのように解決するのですか?

非常に興味深い発見です。これを理解するためには、Java言語仕様書を掘り下げる必要がある( JLS ).

その理由は final は1つしか許可されません。 割り当て . ただし、デフォルト値は 割り当て . 実際には、すべての このような変数 (クラス変数、インスタンス変数、配列コンポーネント) は、その デフォルト値 の前に、最初から アサインメント . そして、最初の代入で参照が変更されます。


クラス変数とデフォルト値

次の例を見てください。

private static Object x;

public static void main(String[] args) {
    System.out.println(x); // Prints 'null'
}

には明示的に値を割り当てていません。 x を指しているが null は、デフォルトの値です。比較すると §4.12.5 :

変数の初期値

クラス変数 インスタンス変数、または配列のコンポーネントは デフォルト値 である場合 作成 ( §15.9 , §15.10.2 )

これは、この例のような種類の変数にのみ当てはまることに注意してください。ローカル変数には当てはまらないので、次の例を参照してください。

public static void main(String[] args) {
    Object x;
    System.out.println(x);
    // Compile-time error:
    // variable x might not have been initialized
}

同じJLSの段落から。

A ローカル変数 ( §14.4 , §14.14 ) である必要があります。 明示的に値を指定する を使用する前に、初期化 ( §14.4 ) または代入 ( §15.26 ),確定代入の規則を用いて検証可能な方法( §16 (確定代入) ).


最終変数

では、次に final からの §4.12.4 :

最終 変数

変数は、以下のように宣言することができます。 最終 . A 最終 変数は 一度だけ割り当てられる . を指定した場合、コンパイルエラーとなります。 最終 変数に代入された場合、その変数が 割り当ての直前に確実に未割り当てであること ( §16 (確定アサインメント) ).


説明

さて、先ほどの例に戻って、少し修正します。

public static void main(String[] args) {
    System.out.println("After: " + X);
}

private static final long X = assign();

private static long assign() {
    // Access the value before first assignment
    System.out.println("Before: " + X);

    return X + 1;
}

を出力します。

Before: 0
After: 1

今まで学んだことを思い出してください。メソッドの内部で assign は、変数 X 割り当てられていない にはまだ値がありません。従って、そのデフォルト値を指します。 クラス変数 JLSによれば、これらの変数は常に即座にデフォルト値を指すようになっています(ローカル変数とは対照的)。この後 assign メソッドは、変数 X に値が代入されます。 1 であり、そのため final は、もう変更できません。ですから、以下のようにすると final :

private static long assign() {
    // Assign X
    X = 1;

    // Second assign after method will crash
    return X + 1;
}


JLSでの例

Andrewのおかげで、まさにこのシナリオをカバーするJLSの段落を見つけました、それはまたそれを実証しています。

しかし、その前に

private static final long X = X + 1;
// Compile-time error:
// self-reference in initializer

メソッドからのアクセスは許可されるのに、なぜこれは許可されないのでしょうか?以下を見てみましょう。 §8.3.3 は、フィールドがまだ初期化されていない場合、フィールドへのアクセスが制限される場合について述べています。

クラス変数に関連するいくつかのルールが記載されています。

クラス変数への単純な名前による参照の場合 f クラスまたはインターフェイスで宣言された C であれば、それは の場合、コンパイル時にエラーが発生します。 :

  • のクラス変数イニシャライザーのどちらかに現れます。 C の静的イニシャライザで、または C ( §8.7 ); および

  • のイニシャライザで参照されます。 f の左側にある。 f の宣言子、および

  • 参照が代入式の左辺にない ( §15.26 ); そして

  • 参照を囲む最も内側のクラスまたはインターフェイスが C .

シンプルに、その X = X + 1 はそのルールに引っかかるが、メソッドのアクセスは引っかからない。このシナリオをリストアップし、例も挙げています。

メソッドによるアクセスは、このようにチェックされませんので。

class Z {
    static int peek() { return j; }
    static int i = peek();
    static int j = 1;
}
class Test {
    public static void main(String[] args) {
        System.out.println(Z.i);
    }
}

は出力を生成します。

0

の変数イニシャライザが i の値にアクセスするためにクラスメソッド peek を使用します。 j 以前 j は変数イニシャライザーによって初期化され、その時点では はまだデフォルト値である ( §4.12.5 ).