メソッドまたはクラスのスコープでは、以下の行がコンパイルされます(警告付き):
int x = x = 1;
クラススコープでは、変数はデフォルト値を取得しますの場合、「未定義の参照」エラーが発生します。
int x = x + 1;
最初のx = x = 1
が同じ「未定義の参照」エラーになるはずではありませんか?または、2行目int x = x + 1
をコンパイルする必要がありますか?それとも私が行方不明になっているものがありますか?
fieldsの場合、b
はb
への不正な前方参照であるため、_int b = b + 1
_は不正です。これを実際に修正するには、_int b = this.b + 1
_を記述します。これは問題なくコンパイルされます。
ローカル変数の場合、d
は使用前に初期化されないため、_int d = d + 1
_は不正です。これは、フィールドの場合はnotであり、常にデフォルトで初期化されます。
コンパイルしようとすると違いがわかります
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 、フィールドの初期化によって管理されます。
フィールドのscopeは、 JLS§6. 、宣言の範囲で定義されています。
関連するルールは次のとおりです。
m
の宣言のスコープは、ネストされた型宣言を含むCの本体全体です。§8.3.2.3によると:
メンバーの宣言は、そのメンバーがクラスまたはインターフェースCのインスタンス(それぞれ静的)フィールドであり、以下のすべての条件が当てはまる場合にのみ、使用される前にテキストで表示する必要があります。
- 使用法は、Cのインスタンス(それぞれ静的)変数初期化子またはCのインスタンス(それぞれ静的)初期化子で発生します。
- 使用法は、割り当ての左側にはありません。
- 使用法は単純な名前を介しています。
- Cは、使用法を囲む最も内側のクラスまたはインターフェイスです。
特定の場合を除き、実際にフィールドを宣言する前に参照できます。これらの制限は、次のようなコードを防ぐことを目的としています
_int j = i;
int i = j;
_
コンパイルから。 Java仕様は、「上記の制限は、コンパイル時に循環またはその他の不正な初期化をキャッチするように設計されている」と述べています。
これらのルールは実際には何に要約されていますか?
要するに、ルールは基本的に、あなたがmustそのフィールドへの参照の前にフィールドを宣言すると言っています。(a)参照が初期化子にある場合、 (c)参照が単純名(_this.
_のような修飾子なし)であり、(d)内部クラス内からアクセスされていない。したがって、4つの条件すべてを満たす前方参照は違法ですが、少なくとも1つの条件で失敗する前方参照は問題ありません。
_int a = a = 1;
_は(b)に違反するためコンパイルされます:参照a
isが割り当てられているため、a
の完全な宣言の前にa
を参照することは正当です。
_int b = this.b + 1
_も違反するためコンパイルします(c):参照_this.b
_は単純な名前ではありません(_this.
_で修飾されています)。 _this.b
_の値はゼロであるため、この奇妙な構造は完全に明確に定義されています。
したがって、基本的に、イニシャライザ内のフィールド参照の制限により、_int a = a + 1
_が正常にコンパイルされません。
最終的なb
は依然として不正な前方参照であるため、フィールド宣言int b = (b = 1) + b
がコンパイルすることをfailに注意してください。
ローカル変数宣言は、 JLS§14.4 、ローカル変数宣言ステートメントによって管理されます。
ローカル変数のscopeは JLS§6. 、宣言の範囲で定義されています:
初期化子は宣言されている変数のスコープ内にあることに注意してください。では、なぜ_int d = d + 1;
_コンパイルしないのですか?
その理由は、確定割り当て( JLS§16 )に関するJavaのルールによるものです。明確な割り当ては、基本的にローカル変数へのすべてのアクセスにはその変数への前の割り当てが必要であり、Javaコンパイラーはループとブランチをチェックして割り当てalwaysが発生することを確認します使用する前に(これが明確な割り当てに専用の仕様セクション全体がある理由です。)基本的なルールは次のとおりです。
x
にアクセスするたびに、アクセスする前にx
を確実に割り当てる必要があります。そうしないと、コンパイル時エラーが発生します。_int d = d + 1;
_では、d
へのアクセスはローカル変数fineに解決されますが、d
にアクセスする前にd
が割り当てられていないため、コンパイラーはエラーを発行します。 _int c = c = 1
_では、最初に_c = 1
_が発生し、c
が割り当てられ、次にc
がその割り当ての結果(1)に初期化されます。
明確な割り当て規則のため、ローカル変数宣言int d = (d = 1) + d;
willは正常にコンパイルされます(nlikeフィールド宣言int b = (b = 1) + b
)、これは、d
が最終的なd
に到達するまでに確実に割り当てられるためです。
int x = x = 1;
に等しい
int x = 1;
x = x; //warning here
にいる間
int x = x + 1;
最初にx+1
を計算する必要がありますが、xの値は不明なのでエラーが発生します(コンパイラはxの値が不明であることを知っています)
おおよそ次のものと同等です。
int x;
x = 1;
x = 1;
まず、int <var> = <expression>;
は常に次と同等です。
int <var>;
<var> = <expression>;
この場合、式はx = 1
であり、これもステートメントです。 var x
はすでに宣言されているため、x = 1
は有効なステートメントです。また、値1の式であり、x
に再び割り当てられます。
Javaまたは任意の現代言語では、割り当ては右から行われます。
2つの変数xとyがある場合、
int z = x = y = 5;
このステートメントは有効であり、これがコンパイラーによる分割方法です。
y = 5;
x = y;
z = x; // which will be 5
しかし、あなたの場合
int x = x + 1;
このように分割されるため、コンパイラは例外を与えました。
x = 1; // oops, it isn't declared because assignment comes from the right.
int x = x = 1;
は次と等しくありません:
int x;
x = 1;
x = x;
javapは再び役立ちます。これらはこのコード用に生成されたJVM命令です。
0: iconst_1 //load constant to stack
1: dup //duplicate it
2: istore_1 //set x to constant
3: istore_1 //set x to constant
次のように:
int x = 1;
x = 1;
未定義の参照エラーをスローする理由はありません。初期化の前に変数が使用されるようになったため、このコードは仕様に完全に準拠しています。 実際には変数の使用はまったくありません、ただの割り当て。そして、JITコンパイラーはさらに進んで、そのような構成を排除します。正直に言って、私はこのコードがJLSの変数の初期化と使用法の仕様にどのように関係しているか理解していません。使用法に問題はありません。 ;)
私が間違っている場合は修正してください。多くのJLSパラグラフを参照する他の回答がなぜ多くのプラスを集めるのか、私にはわかりません。これらの段落には、この場合と共通点はありません。シリアル割り当ては2つだけです。
私たちが書く場合:
int b, c, d, e, f;
int a = b = c = d = e = f = 5;
等しい:
f = 5
e = 5
d = 5
c = 5
b = 5
a = 5
ほとんどの式は、再帰なしで変数に1つずつ割り当てられます。好きなように変数を混乱させることができます:
a = b = c = f = e = d = a = a = a = a = a = e = f = 5;
int x = x + 1;
に1をxに追加するので、x
の値は何ですか、まだ作成されていません。
しかし、int x=x=1;
は、x
に1を割り当てるため、エラーなしでコンパイルされます。
コードの2番目の部分では、宣言の前にxが使用されますが、最初のコードでは、意味がありませんが有効な2回だけ割り当てられます。
それを段階的に、正しい連想で分解しましょう
int x = x = 1
x = 1
、変数xに1を割り当てます
int x = x
、xが何であるかをintとして割り当てます。 xは以前は1として割り当てられていたため、冗長な方法ではありますが1を保持します。
それはうまくコンパイルされます。
int x = x + 1
x + 1
、変数xに1を追加します。ただし、xが未定義の場合、コンパイルエラーが発生します。
int x = x + 1
、したがって、等しい行の右側の部分はコンパイルされず、未割り当て変数に1を追加するため、この行はエラーをコンパイルします。
最初のコードには2番目の=
プラスの代わりに。これはどこでもコンパイルされますが、2番目のコードはどちらの場所でもコンパイルされません。
2番目のint x=x=1
は、値をxに割り当てているためコンパイルされますが、それ以外の場合はint x=x+1
ここで、変数xは初期化されていません。Javaローカル変数はデフォルト値に初期化されていません。注意してください(int x=x+1
)クラススコープでも、変数が作成されないため、コンパイルエラーが発生します。
int x = x + 1;
警告付きでVisual Studio 2008で正常にコンパイルされます
warning C4700: uninitialized local variable 'x' used`