54

次の 2 つのサンプルのうち、最初のサンプルはコンパイルできるのに、2 番目のサンプルはコンパイルできない理由を誰か説明してもらえますか? 唯一の違いは、最初のものは x への参照を '.this' で明示的に修飾しているのに対し、2 番目のものは修飾していないことです。どちらの場合も、最終フィールド x は明らかに初期化前に使用されようとしています。

両方のサンプルが完全に同等に扱われ、両方のコンパイル エラーが発生すると思っていたでしょう。

1)

public class Foo {
    private final int x;
    private Foo() {
        int y = 2 * this.x;
        x = 5;
    }
}

2)

public class Foo {
    private final int x;
    private Foo() {
        int y = 2 * x;
        x = 5;
    }
}
4

4 に答える 4

39

たくさんのスペックを読んで考えた後、私は次のように結論付けました。

Java5またはJava6コンパイラでは、これは正しい動作です。第16章「 Java言語仕様の明確な割り当て、第3版は次のように述べています。

各ローカル変数(§14.4)およびすべての空白final (§4.12.4)フィールド(§8.3.1.2)には、その値へのアクセスが発生したときに、確実に割り当てられた値が必要です。その値へのアクセスは、単純代入演算子の左側のオペランドを除いて、式の任意の場所にある変数の単純な名前で=構成されます。

(私の強調)。したがって、式では、インスタンス変数の単純な名前ではないため2 * this.x、このthis.x部分は「[ 's]値へのアクセス」とは見なされません(したがって、明確な割り当ての規則の対象にはなりません) 。(注:明確な割り当てが発生する場合のルールは、上記のテキストの後の段落で、のようものを許可し、その後は確実に割り当てられると見なされます。カウントされないのはアクセスのルールのみです。)値に注意してください。この場合のofは、 §17.5.2に従ってゼロになります。xthis.xxthis.x = 3xthis.xthis.x

Java 7コンパイラでは、これはコンパイラのバグですが、理解できるバグです。Java言語仕様の第16章「明確な割り当て」、Java 7SEEditionは次のように述べています。

各ローカル変数(§14.4)とすべての空白finalフィールド(§4.12.4§8.3.1.2)には、その値へのアクセスが発生したときに、確実に割り当てられた値が必要です。

その値へのアクセスは、単純代入演算子(§15.26 の左側のオペランドを除いて、式の任意の場所にある変数の単純な名前(または、フィールドの場合は、で修飾されたフィールドの単純な名前)で構成されます。 this1)。=

(私の強調)。したがって、式2 * this.xでは、そのthis.x部分は「[ 's]値へのアクセス」と見なされ、コンパイルエラーが発生するはずです。x

しかし、最初のものコンパイルする必要があるかどうかを尋ねたのではなく、なぜコンパイルするのか(一部のコンパイラーでは)を尋ねました。これは必然的に推測ですが、2つの推測をします。

  1. ほとんどのJava7コンパイラは、Java6コンパイラを変更して作成されています。一部のコンパイラ作成者は、この変更に気付いていない可能性があります。さらに、多くのJava-7コンパイラとIDEは依然としてJava 6をサポートしており、一部のコンパイラ作成者は、Java-6モードで受け入れるJava-7モードで何かを具体的に拒否する意欲を感じていない可能性があります。
  2. 新しいJava7の動作は、奇妙なことに一貫性がありません。のようなもの(false ? null : this).xはまだ許可されており、さらに言えば、(this).xそれでも許可されています。この変更の影響を受けるのは、特定のトークンシーケンスthis.フィールド名だけです。確かに、そのような矛盾は代入ステートメントの左側にすでに存在していましたが(書くことはできますthis.x = 3が、できません(this).x = 3)、それはより簡単に理解できます。this.x = 3それ以外の場合は禁止されている構造の特別に許可されたケースとして受け入れられobj.x = 3ます。それを許可するのは理にかなっています。2 * this.xしかし、他の方法で許可された建設の特別な禁止されたケースとして拒否することは意味がないと思います2 * obj.x、(1)この特別な禁止ケースは括弧を追加することで簡単に回避でき、(2)この特別な禁止ケースは以前のバージョンの言語で許可されていました。また、(3)finalフィールドにのような場合と、のような場合の両方のために、初期化されるまでのデフォルト値(たとえば0、の場合)は、にアクセスするメソッドです。したがって、一部のコンパイラ作成者は、この一貫性のない変更を行う意欲を感じていない可能性があります。int(this).xthis.foo()foo()x

これらのいずれかは驚くべきことです—コンパイラー作成者は仕様に対するすべての変更について詳細な情報を持っていたと思います。私の経験では、Javaコンパイラーは通常、仕様に正確に準拠するのに非常に優れています(すべてのコンパイラーが仕様を持っている一部の言語とは異なります)。独自の方言)—しかし、まあ、何かが起こった、そして上記は私の唯一の2つの推測です。

于 2012-12-13T19:29:33.437 に答える
2

thisコンストラクターで使用すると、コンパイラーは xオブジェクトのメンバー属性として認識されthis ます (デフォルトで初期化されます)xisであるためint、デフォルトで で初期化され0ます。これにより、コンパイラが満足し、実行時にも正常に動作します。

を使用しない場合this、コンパイラはx字句解析で宣言を直接使用しているため、初期化について不平を言います(コンパイル時現象)。

したがって、これは の定義ですthis。これにより、コンパイラxは、コンパイルでの語彙分析中にオブジェクトのメンバー変数と直接属性として分析し、異なるコンパイル動作が発生します。

一次式として使用される場合、キーワード this は、インスタンス メソッドが呼び出されたオブジェクト (§15.12) への参照、または構築中のオブジェクトへの参照である値を示します。

于 2012-12-13T16:53:14.043 に答える
-1

Eclipseでの動作を参照していると思います。(コメントとして述べられているように、javacを使用したコンパイルは機能します)。

これはEclipseの問題だと思います。独自のコンパイラと独自のルールセットがあります。それらの1つは、Java-commpilerが変数を初期化する場合でも、初期化されていないフィールドにアクセスできないことです。

于 2012-12-13T16:56:55.067 に答える
-1

コンパイラは、this.x を記述することで「this」が存在することを意味すると推定しているため、コンストラクターが呼び出された (そして最終変数が初期化された) と推定していると思います。ただし、実行しようとすると RuntimeException が発生するはずです

于 2012-12-13T16:52:38.657 に答える