tl; dr
以下のためにフィールド、int b = b + 1
ので違法であるb
と違法前方参照ですb
。これを実際に修正するには、を記述します。これは、問題int b = this.b + 1
なくコンパイルされます。
以下のために、ローカル変数、int d = d + 1
ので違法であるd
、使用する前に初期化されていません。これは、常にデフォルトで初期化されるフィールドには当てはまりません。
コンパイルしてみると違いがわかります
int x = (x = 1) + x;
フィールド宣言およびローカル変数宣言として。前者は失敗しますが、セマンティクスの違いにより、後者は成功します。
前書き
まず、フィールドとローカル変数の初期化子のルールは大きく異なります。したがって、この答えは2つの部分でルールに取り組みます。
このテストプログラムは、全体を通して使用します。
public class test {
int a = a = 1;
int b = b + 1;
public static void Main(String[] args) {
int c = c = 1;
int d = d + 1;
}
}
の宣言b
は無効であり、illegal forward reference
エラーが発生して失敗します。
の宣言d
は無効であり、variable d might not have been initialized
エラーが発生して失敗します。
これらのエラーが異なるという事実は、エラーの理由も異なることを示唆しています。
田畑
Javaのフィールド初期化子は、JLS§8.3.2、フィールドの初期化によって管理されます。
スコープフィールドのはで定義されているJLS§6.3、宣言のスコープ。
関連するルールは次のとおりです。
m
クラス型C(§8.1.6)で宣言または継承されたメンバーの宣言のスコープは、ネストされた型宣言を含むCの本体全体です。
- インスタンス変数の初期化式では、クラスで宣言または継承された静的変数の単純な名前を使用できます。
- これらのインスタンス変数がスコープ内であっても、宣言が使用後にテキストで表示されるインスタンス変数の使用が制限される場合があります。インスタンス変数への前方参照を管理する正確な規則については、8.3.2.3を参照してください。
8.3.2.3は言う:
メンバーの宣言は、そのメンバーがクラスまたはインターフェイスCのインスタンス(それぞれ静的)フィールドであり、次のすべての条件が満たされている場合にのみ、使用前にテキストで表示する必要があります。
- 使用法は、Cのインスタンス(それぞれ静的)変数初期化子またはCのインスタンス(それぞれ静的)初期化子で発生します。
- 使用法は割り当ての左側ではありません。
- 使い方は単純な名前です。
- Cは、使用法を囲む最も内側のクラスまたはインターフェースです。
特定の場合を除いて、フィールドは宣言される前に実際に参照できます。これらの制限は、次のようなコードを防ぐことを目的としています
int j = i;
int i = j;
コンパイルから。Java仕様には、「上記の制限は、コンパイル時に循環または初期化されていない不正な初期化をキャッチするように設計されている」とあります。
これらのルールは実際にはどのように要約されますか?
要するに、ルールは基本的にあなたがいることを言わなければならないの参照がある()参照(b)は、初期化子である参照が割り当てされていない場合(c)は、そのフィールドへの参照の前にフィールドを宣言します単純な名前(のような修飾子なしthis.
)および(d)内部クラス内からアクセスされていない。したがって、4つすべての条件を満たす前方参照は不正ですが、少なくとも1つの条件で失敗する前方参照は問題ありません。
int a = a = 1;
(b)に違反しているためコンパイルします。参照a
が割り当てられているため、の完全な宣言のa
前に参照することは合法a
です。
int b = this.b + 1
(c)に違反しているため、コンパイルも行われます。参照this.b
は単純な名前ではありません(これはで修飾されていますthis.
)。この奇妙な構成はthis.b
、値がゼロであるため、完全に明確に定義されています。
したがって、基本的に、初期化子内のフィールド参照の制限により、int a = a + 1
正常にコンパイルできません。
final は依然として不正な前方参照であるため、フィールド宣言int b = (b = 1) + b
がコンパイルに失敗することに注意b
してください。
ローカル変数
ローカル変数宣言は、JLS§14.4のローカル変数宣言ステートメントによって管理されます。
スコープローカル変数が定義されているJLS§6.3、宣言のスコープ:
- ブロック内のローカル変数宣言のスコープ(§14.4)は、宣言が出現するブロックの残りの部分であり、独自のイニシャライザから始まり、ローカル変数宣言ステートメントの右側にさらに宣言子を含めます。
初期化子は宣言されている変数のスコープ内にあることに注意してください。では、なぜint d = d + 1;
コンパイルしないのですか?
その理由は、明確な割り当てに関するJavaの規則によるものです(JLS§16)。明確な割り当ては基本的に、ローカル変数へのすべてのアクセスにはその変数への先行割り当てが必要であり、Javaコンパイラーはループと分岐をチェックして、割り当てが使用の前に常に発生することを確認します(これにより、明確な割り当てには仕様セクション全体が専用になりますそれに)。基本的なルールは:
- ローカル変数や空白の最終フィールドのすべてのアクセスに関しては
x
、x
確かにアクセスする前に割り当てられた、またはコンパイル時エラーが発生しなければなりません。
ではint d = d + 1;
、へのアクセスd
はローカル変数fineに解決されますが、アクセスd
される前に割り当てられていないためd
、コンパイラーはエラーを発行します。その割り当ては、まず起こり、次いで、及び(1)その割り当ての結果に初期化されます。int c = c = 1
c = 1
c
c
明確な割り当てルールのため、ローカル変数宣言int d = (d = 1) + d;
は(フィールド宣言とは異なりint b = (b = 1) + b
)正常にコンパイルd
されd
ます。これは、最終に到達するまでに確実に割り当てられるためです。
static
をクラススコープ変数に追加static int x = x + 1;
すると、同じエラーが発生しますか?C#では静的か非静的かによって違いが生じるためです。