SO質問 Will Java最終変数はデフォルト値を持っていますか? に似ていますが、その答えは完全には解決しませんこれは、その質問がインスタンス初期化ブロック内でxの値を直接出力しないためです。
問題は、ブロックの終了前にxに値を割り当てながら、インスタンス初期化ブロック内でxを直接印刷しようとすると発生します。
class HelloWorld {
final int x;
{
System.out.println(x);
x = 7;
System.out.println(x);
}
HelloWorld() {
System.out.println("hi");
}
public static void main(String[] args) {
HelloWorld t = new HelloWorld();
}
}
これにより、変数xが初期化されていない可能性があることを示すコンパイル時エラーが発生します。
$ javac HelloWorld.Java
HelloWorld.Java:6: error: variable x might not have been initialized
System.out.println(x);
^
1 error
直接印刷する代わりに、印刷する関数を呼び出しています:
class HelloWorld {
final int x;
{
printX();
x = 7;
printX();
}
HelloWorld() {
System.out.println("hi");
}
void printX() {
System.out.println(x);
}
public static void main(String[] args) {
HelloWorld t = new HelloWorld();
}
}
これは正しくコンパイルされ、出力されます
0
7
hi
2つのケースの概念的な違いは何ですか?
JLSでは、 §8.3.3。フィールド初期化中の前方参照 、次の場合にコンパイル時エラーがあると述べています。
これらのインスタンス変数はスコープ内にありますが、使用が制限されている場合がありますが、その宣言がテキストで表示されるインスタンス変数の使用は制限されています。具体的には、次のすべてに該当する場合、コンパイル時エラーです。
クラスまたはインターフェイスCでのインスタンス変数の宣言は、インスタンス変数の使用後にテキストで表示されます。
使用法は、Cのインスタンス変数初期化子またはCのインスタンス初期化子の単純な名前です。
使用は割り当ての左側にはありません。
Cは、使用を囲む最も内側のクラスまたはインターフェイスです。
次のルールにはいくつかの例がありますが、最も近いのはこの例です。
_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
の初期化子は、j
がその変数初期化子によって初期化される前に、クラスメソッドpeek()
を使用して変数j
の値にアクセスします。デフォルト値のままです( §4.12.5変数の初期値 )。
要約すると、2番目の例はコンパイルして正常に実行されます。これは、printX()
を呼び出すときとprintX()
を呼び出すときに、x
変数が既に初期化されているかどうかをコンパイラがチェックしないためです実際には実行時に行われ、x
変数にはデフォルト値(_0
_)が割り当てられます。
JLSを読むと、答えは セクション16.2.2 にあるように見えます。
空白の
final
メンバーフィールドV
は、V
のスコープ内のメソッドの本体であるブロック(§14.2)の前、およびV
のスコープ内で宣言されたクラスの宣言の前に、確実に割り当てられます(さらに、確実に割り当て解除されません)。
つまり、メソッドが呼び出されると、最終フィールドは呼び出す前にデフォルト値0に割り当てられるため、メソッド内で参照すると、正常にコンパイルされ、値0が出力されます。
ただし、メソッド外のフィールドにアクセスすると、フィールドは未割り当てと見なされるため、コンパイルエラーが発生します。次のコードもコンパイルされません。
public class Main {
final int x;
{
method();
System.out.println(x);
x = 7;
}
void method() { }
public static void main(String[] args) { }
}
理由:
V
は、ブロック内の他のステートメントS
の前に[未]割り当てられます。ただし、V
は、ブロック内のS
の直前のステートメントの後に[未]割り当てられます。
最終フィールドx
はメソッド呼び出しの前に割り当てられていないため、その後も割り当てられていません。
JLSのこの注記も関連しています。
V
は、C
で宣言されたコンストラクター、メソッド、インスタンス初期化子、または静的初期化子の本体であるブロックの前に確実に割り当て解除されると結論付ける規則はないことに注意してください。V
は、Cで宣言されたコンストラクター、メソッド、インスタンス初期化子、または静的初期化子の本体であるブロックの前に明確に割り当て解除されていないと非公式に結論付けることができますが、そのような規則を明示的に記述する必要はありません。
違いは、最初のケースでは、initializer blockからSystem.out.println
を呼び出しているため、コンストラクターの前に呼び出されるブロックです。最初の行で
System.out.println(x);
変数x
はまだ初期化されていないため、コンパイルエラーが発生します。
しかし、2番目のケースでは、変数が既に初期化されているかどうかわからないインスタンスメソッドを呼び出すため、コンパイルエラーが発生せず、x
のデフォルト値を確認できます。
OK、ここに私の2セントがあります。
最終的な変数は、コンストラクターで宣言中または後でのみ初期化できることを知っています。その事実を念頭に置いて、ここで何が起こったのか見てみましょう。
エラーなしケース:
したがって、メソッド内で使用する場合、すでに値があります。
1) If you initialize it, that value.
2) If not, the default value of data type.
エラーの場合:
初期化ブロックでそれを行うと、エラーが表示されます。
{
// whatever code is needed for initialization goes here
}
そして
Javaコンパイラは初期化子ブロックをすべてのコンストラクタにコピーします。したがって、このアプローチはコードのブロックを共有するために使用できます複数のコンストラクター間。
コンパイラの目では、コードは文字通りに等しい
class HelloWorld {
final int x;
HelloWorld() {
System.out.println(x); ------------ ERROR here obviously
x = 7;
System.out.println(x);
System.out.println("hi");
}
public static void main(String[] args) {
HelloWorld t = new HelloWorld();
}
}
それを初期化する前に使用しています。
ケース1:
コンパイルエラーが発生します。
なぜならSystem.out.println(x);
で
初期化されなかったxを印刷しようとしています。
ケース2:
リテラル値を直接使用しておらず、代わりに何らかのメソッドを呼び出しているため、正常に機能します。
一般ルールは、
初期化されていない変数にアクセスしようとすると、コンパイルエラーが発生します。
ここでは、初期化ブロックを扱います。 Javaコンパイラは、初期化子ブロックをすべてのコンストラクタにコピーします。
Xの印刷は別のフレームにあるため、2番目の例ではコンパイラエラーは発生しません。仕様を参照してください。