web-dev-qa-db-ja.com

なぜこのJavaコードがコンパイルされるのですか?

メソッドまたはクラスのスコープでは、以下の行がコンパイルされます(警告付き):

int x = x = 1;

クラススコープでは、変数はデフォルト値を取得しますの場合、「未定義の参照」エラーが発生します。

int x = x + 1;

最初のx = x = 1が同じ「未定義の参照」エラーになるはずではありませんか?または、2行目int x = x + 1をコンパイルする必要がありますか?それとも私が行方不明になっているものがありますか?

96
Marcin

tl; dr

fieldsの場合、bbへの不正な前方参照であるため、_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. 、宣言の範囲で定義されています。

関連するルールは次のとおりです。

  • クラス型C(8.1.6)で宣言または継承されたメンバーmの宣言のスコープは、ネストされた型宣言を含むCの本体全体です。
  • インスタンス変数の初期化式では、クラスで宣言された、またはクラスによって継承された静的変数の単純な名前を使用できます。
  • これらのインスタンス変数はスコープ内にありますが、使用が制限されている場合がありますが、その宣言がテキストで表示されるインスタンス変数の使用は制限されています。インスタンス変数への前方参照を管理する正確な規則については、8.3.2.3を参照してください。

§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)に違反するためコンパイルされます:参照aisが割り当てられているため、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 、ローカル変数宣言ステートメントによって管理されます。

ローカル変数のscopeJLS§6. 、宣言の範囲で定義されています:

  • ブロック内のローカル変数宣言(§14.4)のスコープは、宣言が現れる残りのブロックであり、独自のイニシャライザーから始まり、ローカル変数宣言ステートメントの右側にさらに宣言子を含めます。

初期化子は宣言されている変数のスコープ内にあることに注意してください。では、なぜ_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に到達するまでに確実に割り当てられるためです。

101
nneonneo
int x = x = 1;

に等しい

int x = 1;
x = x; //warning here

にいる間

int x = x + 1; 

最初にx+1を計算する必要がありますが、xの値は不明なのでエラーが発生します(コンパイラはxの値が不明であることを知っています)

86
msam

おおよそ次のものと同等です。

int x;
x = 1;
x = 1;

まず、int <var> = <expression>;は常に次と同等です。

int <var>;
<var> = <expression>;

この場合、式はx = 1であり、これもステートメントです。 var xはすでに宣言されているため、x = 1は有効なステートメントです。また、値1の式であり、xに再び割り当てられます。

41
OpenSauce

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;
8
Mikhail

int x = x + 1;に1をxに追加するので、xの値は何ですか、まだ作成されていません。

しかし、int x=x=1;は、xに1を割り当てるため、エラーなしでコンパイルされます。

7
Alya'a Gamal

コードの2番目の部分では、宣言の前にxが使用されますが、最初のコードでは、意味がありませんが有効な2回だけ割り当てられます。

5
WilQu

それを段階的に、正しい連想で分解しましょう

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を追加するため、この行はエラーをコンパイルします。

5
steventnorris

最初のコードには2番目の=プラスの代わりに。これはどこでもコンパイルされますが、2番目のコードはどちらの場所でもコンパイルされません。

5
Joe Elleson

2番目のint x=x=1は、値をxに割り当てているためコンパイルされますが、それ以外の場合はint x=x+1ここで、変数xは初期化されていません。Javaローカル変数はデフォルト値に初期化されていません。注意してください(int x=x+1)クラススコープでも、変数が作成されないため、コンパイルエラーが発生します。

3
Krushna
int x = x + 1;

警告付きでVisual Studio 2008で正常にコンパイルされます

warning C4700: uninitialized local variable 'x' used`
2
izogfif

xはx = x + 1;で初期化されません。

Javaプログラミング言語は静的に型付けされているため、すべての変数は使用する前に最初に宣言する必要があります。

プリミティブデータ型 を参照してください

2
Mohan Raj B