Java Final変数にはデフォルト値がありますか?


81

私はこのようなプログラムを持っています:

class Test {

    final int x;

    {
        printX();
    }

    Test() {
        System.out.println("const called");
    }

    void printX() {
        System.out.println("Here x is " + x);
    }

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

}

それを実行しようとすると、次のようなコンパイラエラーが発生します:variable x might not have been initializedJavaのデフォルト値に基づいて、以下の出力を正しく取得する必要がありますか?

"Here x is 0".

最終変数にはdafault値がありますか?

このようにコードを変更すると、

class Test {

    final int x;

    {
        printX();
        x = 7;
        printX();
    }

    Test() {
        System.out.println("const called");
    }

    void printX() {
        System.out.println("Here x is " + x);
    }

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

}

次のように出力されます:

Here x is 0                                                                                      
Here x is 7                                                                                     
const called

誰かがこの振る舞いを説明できますか。

回答:


62

http://docs.oracle.com/javase/tutorial/java/javaOO/initial.html、「インスタンスメンバーの初期化」の章:

Javaコンパイラは、初期化ブロックをすべてのコンストラクタにコピーします。

つまり、次のようになります。

{
    printX();
}

Test() {
    System.out.println("const called");
}

次のように動作します。

Test() {
    printX();
    System.out.println("const called");
}

ご覧のとおり、インスタンスが作成されると、最後のフィールドは明確に割り当てられていませんが、(http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.htmlから)#jls-8.3.1.2):

空白の最終インスタンス変数は、それが宣言されているクラスのすべてのコンストラクターの最後に確実に割り当てる必要があります。そうしないと、コンパイル時エラーが発生します。

ドキュメントには明示的に記載されていないようですが(少なくとも私はそれを見つけることができませんでした)、最終フィールドはコンストラクターの終了前に一時的にデフォルト値を取得する必要があります。これにより、次の場合に予測可能な値が得られます。割り当てる前に読んでください。

デフォルト値:http//docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.12.5

2番目のスニペットでは、インスタンスの作成時にxが初期化されるため、コンパイラは文句を言いません。

Test() {
    printX();
    x = 7;
    printX();
    System.out.println("const called");
}

また、次のアプローチは機能しないことに注意してください。最終変数のデフォルト値の使用は、メソッドを介してのみ許可されます。

Test() {
    System.out.println("Here x is " + x); // Compile time error : variable 'x' might not be initialized
    x = 7;
    System.out.println("Here x is " + x);
    System.out.println("const called");
}

1
super()への暗黙的(または明示的)呼び出しが例の1つでどこに行くかは注目に値するかもしれません。
パトリック

2
これは、finalフィールドを初期化しないとコンパイルエラーが発生する理由には答えません。
2014

@ sp00m良い参考資料-私はそれを銀行に預けます。
ボヘミアン

2
@justhalf答えには、重要なポイントがありません。ファイナルには(メソッドを介して)デフォルトの状態でアクセスできますが、構築プロセスが終了する前に初期化しないと、コンパイラーは文句を言います。そのため、2回目の試行は機能しますが(実際にはxを初期化します)、最初の試行は機能しません。空白のファイナルに直接アクセスしようとすると、コンパイラも文句を言います。
ルカ

28

JLSは、コンストラクター(またはほとんど同じである初期化ブロック)の空白の最終インスタンス変数にデフォルト値を割り当てる必要があると言っています。そのため、最初のケースでエラーが発生します。ただし、以前はコンストラクターでアクセスできないとは言っていません。少し奇妙に見えますが、割り当ての前にアクセスして、int-0のデフォルト値を確認できます。

UPD。@ I4mpiで述べたように、JLS 、アクセスする前に各値を確実に割り当てる必要があるというルールを定義しています。

Each local variable (§14.4) and every blank final field (§4.12.4, §8.3.1.2) must have a definitely assigned value when any access of its value occurs.

ただし、コンストラクターとフィールドに関しても興味深いルールがあります。

If C has at least one instance initializer or instance variable initializer then V is [un]assigned after an explicit or implicit superclass constructor invocation if V is [un]assigned after the rightmost instance initializer or instance variable initializer of C.

したがって、2番目のケースでは、値xはコンストラクターの最後に割り当てが含まれているため、コンストラクターの最初に確実に割り当てられます。


実際に、それはありませんあなたが代入する前にそれにアクセスすることはできませんと言う:「それぞれのローカル変数(14.4)と、すべての空白の最終フィールド(§4.12.4を、§8.3.1.2)その値のいずれかのアクセスが発生したときに確実に割り当てられた値を持っている必要があります"
l4mpi 2014

1
「確実に割り当てられる」必要がありますが、このルールはコンストラクターに関して奇妙な動作をします。答えを更新しました
udalmik 2014

いくつかの複雑な条件に応じて、finalフィールドを読み取る場合と読み取らない場合があるコードの方法があり、そのコードがフィールドの書き込みの前後の両方で実行される場合、コンパイラーは一般にそれが書かれる前にそれが実際にフィールドを読むかどうかを知ること。
スーパーキャット2014

7

初期化しないと、が初期化されないxため、コンパイル時エラーが発生しxます。

xfinalとして宣言するということは、コンストラクターまたはinitializer-blockでのみ初期化できることを意味します(このブロックはコンパイラーによってすべてのコンストラクターにコピーされるため)。

0変数が初期化される前に出力される理由は、マニュアルで定義されている動作によるものです(「デフォルト値」セクションを参照)。

デフォルト値

フィールドが宣言されるときに値を割り当てる必要は必ずしもありません。宣言されているが初期化されていないフィールドは、コンパイラによって適切なデフォルトに設定されます。一般的に、このデフォルトは、データ型に応じてゼロまたはnullになります。ただし、このようなデフォルト値に依存することは、一般的に悪いプログラミングスタイルと見なされます。

次のグラフは、上記のデータ型のデフォルト値をまとめたものです。

Data Type   Default Value (for fields)
--------------------------------------
byte        0
short       0
int         0
long        0L
float       0.0f
double      0.0d
char        '\u0000'
String (or any object)      null
boolean     false

4

最初のエラーは、コンパイラが最終フィールドがあると文句を言っていますが、それを初期化するコードがないことです。

2番目の例では、値を割り当てるコードがありますが、実行シーケンスは、フィールドを割り当てる前と後の両方でフィールドを参照することを意味します。

任意のフィールドに事前に割り当てられた値がデフォルト値です。


2

クラスのすべての非最終フィールドはデフォルト値に初期化されます(0数値データ型、falseブール値、およびnull参照型の場合、複合オブジェクトと呼ばれることもあります)。これらのフィールドは、コンストラクターの前または後にフィールドが宣言されているかどうかに関係なく、コンストラクター(またはインスタンス初期化ブロック)が実行される前に初期化されます。

クラスのfinalフィールドにはデフォルト値なく、クラスコンストラクターがジョブを終了する前に一度だけ明示的に初期化する必要があります。

実行ブロック内のローカル変数(メソッドなど)にはデフォルト値がありません。これらのフィールドは、最初に使用する前に明示的に初期化する必要があり、ローカル変数がfinalとしてマークされているかどうかは関係ありません。


1

私ができる最も簡単な言葉でそれを言いましょう。

final変数は初期化する必要があります。これは言語仕様で義務付けられています。とはいえ、宣言時に初期化する必要はありませんのでご注意ください。

オブジェクトを初期化する前に、それを初期化する必要があります。

初期化ブロックを使用して、最終変数を初期化できます。現在、初期化ブロックには2つのタイプが staticあり、non-static

使用したブロックは非静的初期化ブロックです。したがって、オブジェクトを作成すると、Runtimeはコンストラクターを呼び出し、次にコンストラクターが親クラスのコンストラクターを呼び出します。

その後、すべての初期化子(この場合は非静的初期化子)を呼び出します。

あなたの質問では、ケース1:初期化ブロックの完了後でも、最終変数は初期化されていないままです。これは、コンパイラが検出するエラーです。

ケース2:初期化子が故に、コンパイラは、オブジェクトが初期化される前に、最終的にはすでに初期化されていることを知って、最終的に変数を初期化します。したがって、文句を言うことはありません。

さて、問題は、なぜxゼロを取るのかということです。ここでの理由は、コンパイラはエラーがないことをすでに知っているため、initメソッドを呼び出すと、すべてのファイナルがデフォルトに初期化され、と同様に実際の代入ステートメントで変更できるフラグが設定されx=7ます。以下のinit呼び出しを参照してください。

ここに画像の説明を入力してください


1

私の知る限り、コンパイラーは常にクラス変数をデフォルト値(最終変数であっても)に初期化します。たとえば、intをそれ自体に初期化する場合、intはデフォルトの0に設定されます。以下を参照してください。

class Test {
    final int x;

    {
        printX();
        x = this.x;
        printX();
    }

    Test() {
        System.out.println("const called");
    }

    void printX() {
        System.out.println("Here x is " + x);
    }

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

上記は以下を出力します:

Here x is 0
Here x is 0
const called

1
最後の変数xは、OPのコードでは静的ではありません。
JamesB 2014

OPのコードをthis.xに初期化するように簡単に変更することもでき、同じことが起こります。静的であるかどうかは関係ありません。
Michael D.

OPの質問を読んでいないように見えるので、ここで静的コンテンツを削除することをお勧めします。
JamesB 2014

OPのコードからベースラインを作成すると役に立ちますか?私が言ったように、変数が静的であるかどうかは関係ありません。私のポイントは、変数をそれ自体に初期化してデフォルト値を取得することは、変数が明示的に初期化される前に暗黙的に初期化されることを意味するということです。
Michael D.

1
あなたがアクセスしようとしているので、それが初期化される前に、ライン6上に、(直接)、最終的に変数をコンパイルしていません
ルカ

1

それを実行しようとすると、次のようなコンパイラエラーが発生します:変数xがJavaのデフォルト値に基づいて初期化されていない可能性があります以下の出力を正しく取得する必要がありますか?

「ここでxは0です。

いいえ。そもそもコンパイル時エラーが発生しているため、その出力は表示されません。最終変数はデフォルト値を取得しますが、Java言語仕様(JLS)では、コンストラクターの最後までに初期化する必要があります(LE:ここに初期化ブロックを含めます)。そうしないと、コンパイル時エラーが発生します。コードがコンパイルおよび実行されないようにします。

2番目の例は要件を尊重します。そのため、(1)コードがコンパイルされ、(2)期待される動作が得られます。

将来的には、JLSに慣れるようにしてください。Java言語に関するこれ以上の情報源はありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.