2009-07-27 14 views
6

私はこのコード静的イニシャライザで宣言の順序が重要なのはなぜですか?

private static Set<String> myField; 

static { 
    myField = new HashSet<String>(); 
    myField.add("test"); 
} 

を持っており、それが動作します。しかし、私が注文を裏返すと、私は不正な前方参照エラーが発生します。

static { 
    myField = new HashSet<String>(); 
    myField.add("test"); // illegal forward reference 
} 

private static Set<String> myField; 

私は少しショックを受けよ、私は、Javaからこのような何かを期待していませんでした。 :)

ここではどうなりますか?宣言の順序が重要なのはなぜですか?なぜ代入は動作しますが、メソッド呼び出しは動作しませんか?

答えて

10

まず、「前方参照」とは何か、そしてなぜそれが悪いのかを考えてみましょう。前方参照は、まだ初期化されていない変数への参照であり、静的な初期化子にのみ限定されるものではありません。許可されていれば、予期せぬ結果が出るため、これは単に悪いことです。このコードを見てください:

public class ForwardRef { 
    int a = b; // <--- Illegal forward reference 
    int b = 10; 
} 

このクラスを初期化するときはどうすればよいですか?クラスが初期化されると、最初から最後に発生した順に初期化が実行されます。したがって、あなたがライン

a = b; 

前に実行することを期待したい:この種の問題を避けるために

b = 10; 

、Javaの設計者は、完全に前方参照のような使用を許可しません。この動作は、section 8.3.2.3 of Java Language Specificationsによって指定さ

EDIT

:部材(それぞれ静的)インスタンスである場合にのみ使用される前に、部材の宣言を表示する必要が

Aの分野クラスまたはインタフェースのCと、次の条件の全てが成立:

  • 使用インスタンスで発生し(各静的)変数初期化子またはCのインスタンス(それぞれ静的な)初期化子。

  • 使用法は割り当ての左側にありません。

  • Cは、使用法を囲む最も内側のクラスまたはインターフェイスです。

上記の3つの要件のいずれかが満たされない場合、コンパイル時エラーが発生します。

+0

OK、わかりました。しかし、最初の割り当て後、myField *は初期化されます。なぜ私はまだaddメソッドを呼び出せないのですか? –

+0

これらの3つの要件が存在しない場合は、イニシャライザでローカル変数を使用して暗黙的な前方参照を作成できますか?それがこれらの制限の理由ですか? –

+0

ths JLSはこう言っています: "...これらの制限は、コンパイル時に循環型またはその他の形式の初期化をキャッチするように設計されています...." –

1

Javaでは、静的またはそれ以外のすべての初期化子は、クラス定義に現れる順序で評価されます。

+2

しかし、私は質問が*なぜ*ではないと思いますか? –

+0

なぜaddメソッドを呼び出すことはできませんが、割り当てはできません。 –

0

myFieldの参照型なしで使用するadd()メソッドをコンパイラが判断できないため、メソッド呼び出しが問題になると思います。

実行時に、使用されるメソッドはオブジェクト型によって決まりますが、コンパイラは参照型のみを知っています。

+0

これは問題ではないと思います。コンパイラはコンパイル時にメソッドをバインドすることはほとんどできません。 VMTが使用されている場所は次のとおりです。http://en.wikipedia.org/wiki/Virtual_method_table –

1

the JLSで前方参照のルールを参照してください。使用インスタンスで発生

  • Cのまたはインスタンスに(それぞれ静的)変数初期化子(それぞれ静的)C.
  • の初期使用が左手にない:場合は、前方参照を使用することはできません割り当ての側
  • 使い方は単純な名前です。
  • Cは、使用法を囲む最も内側のクラスまたはインターフェイスです。

これらのすべてがあなたの例であるため、前方参照は無効です。

2

これを試してみてください。

class YourClass { 
    static { 
     myField = new HashSet<String>(); 
     YourClass.myField.add("test"); 
    } 

    private static Set<String> myField; 
} 

それはJLSに応じてエラーなしでコンパイルする必要があります...
(本当に助け、またはしていない?)

+0

これはテストされていませんが、これは引き続きJLSの「左側のルール」に違反しています。なぜ私の答えを参照してください。 – rtperson

+0

私はそれをテストし、それが動作します。今私はもう何も理解していません... –

+1

@DR:これはコンパイラ機能です。リフレクションは、多くのコンパイラチェックを回避するために使用できます。クラスの外部からプライベートメソッドを呼び出します。 –

1

をDFAの答えについて詳しく説明するには:

JLS 8.2.3.2の2番目の箇条書きの点で、あなたを引き上げるのは「左手側」ルールだと思います。初期設定では、myFieldは左側にあります。追加する呼び出しでは、右側に表示されます。ここでのコードは、暗黙的である:あなたは結果を評価していないが、それはありますかのように、コンパイラがまだ機能し

boolean result = myField.add('test') 

。 add()への呼び出しが失敗している間に、あなたの初期化が成功する理由です。

については、なぜこれはそうですが、わかりません。私が知っていることは、JVM開発者の便宜のためかもしれません。

+0

これが当てはまる場合、voidメソッドは機能しませんか? –

+0

まあ、まったく同じ理由で。あなたがそれに割り当てていないので、voidを返す呼び出しはまだ右側にあります。関数には出力がありませんが、それが関数であることは変わっていません。 – rtperson