2016-08-17 14 views
7

列挙型コンストラクタが静的フィールド とそのメソッド内のメソッドにアクセスできない理由を理解しています。なぜそれがクラス内で が許可されているのですか?例えばSE次のコード、エンクロージングクラスのプライベート列挙型と静的フィールド

import java.util.ArrayList; 
import java.util.List; 

public enum Foo { 
    A("Some string"), 
    B("Some other string"), 
    ; 

    static List<String> list = new ArrayList<>(); 

    Foo(String description) { 
     list.add(description); 
    } 
} 

このコードは、コンパイル時エラーが発生しillegal reference to static field from initializer

関連する背景

静的フィールドはすべて 初期化されている前に、列挙型のコンストラクタが呼び出されます。上の例では、これはlistがまだ初期化されていないことを意味します。静的フィールドは(section 12.4.2)

次に、テキスト順に、クラス変数初期化子及びクラスの静的 イニシャライザ、または インタフェースのフィールド初期化子のいずれかを実行する、などのテキスト ために初期化されるからです彼らは単一のブロックだった。

(強調鉱山)

と列挙型自体は常に静的フィールドを含む任意の他の のフィールドを、先行する値以来、彼らは 列挙型のコンストラクタに利用できない、何の静的フィールドが列挙型 をpreceedないこと、すなわち、値A、およびBです。

質問

しかし、ここで私の質問は、なぜそれがあることではあるenum がかどうかにかかわらず列挙型の、 をその外側のクラスの静的フィールドにアクセスすることができます(クラスの内部に封入)「プライベート」前に表示---または--- 静的フィールドの後に?特に、Java仕様のどこにこれが指定されていますか?

は参照

import java.util.ArrayList; 
import java.util.List; 

public class Bar { 
    static List<String> first = new ArrayList<>(); 

    enum Baz { 
     A("Some string"), 
     B("Some other string"), 
     ; 


     Baz(String description) { 
      // Can access static fields from before the enum 
      first.add(description); 

      // Can access static fields from _after_ the enum 
      second.add(description); 
     } 
    } 

    static List<String> second = new ArrayList<>(); 
} 
+0

興味深い質問です。私は静的フィールドの初期化が正しい前に、あなたのコンストラクタが呼ばれているとは思わない。私はそれがむしろ反対であると思う。証明書のソート:http://stackoverflow.com/questions/36407743/enum-based-table-matrix-in-java –

+0

@SotiriosDelimanolis実際にenum定数のリストは、enumのコンストラクタを使用して作成されたオブジェクトのリストです。 –

+0

@SotiriosDelimanolisの例が提供されました。ありがとうございます。そしてごめんなさい。 –

答えて

4

これは、JLSのあらゆる場所にあります。 When Initialization Occurs chapter状態

目的は、クラスまたはインタフェースタイプが整合した状態に置きイニシャライザ のセットを有すること、そしてこの状態は他のクラスによって観察される最初 状態であることです。 static初期化子と クラス変数初期化子は、テキスト形式の順序で実行され、そして は、その宣言これらのクラス変数がスコープ内 (§8.3であっても、使用後にテキストで表示され クラスに宣言したクラス変数を参照しない場合があります。 3)。この制限は、 のコンパイル時に、ほとんどの円形またはそれ以外の形式の初期化を検出するように設計されています。

太字のスニペットは、直接アクセスを含むクラスを指します。

enum種類がhere

列挙型の宣言は、新しい列挙型、クラス型の特別な種類を指定し、Java言語仕様で定義されています。あなたはBazコンストラクタ

Baz(String description) { 
    // Can access static fields from before the enum 
    first.add(description); 

    // Can access static fields from _after_ the enum 
    second.add(description); 
} 

にアクセス

フィールドはクラス変数はクラス、列挙型Bazを宣言されていません。したがって、アクセスは許可されます。

detailed class initialization procedureでは、各クラス(クラス、インターフェイス、列挙型)が個別に初期化されることを説明しています。しかし、我々はまだ、初期化される値

public class Example { 
    public static void main(String[] args) throws Exception { 
     new Bar(); 
    } 
} 

class Bar { 
    static Foo foo = Foo.A; 
    static Integer max = 42; 

    enum Foo { 
     A; 

     Foo() { 
      System.out.println(max); 
     } 
    } 
} 

これはnullを印刷しますを見ている例を作成することができます。 maxへのアクセスはenumタイプのコンストラクタで許可されていますが、プログラムの実行はmaxが初期化される前にアクセスに達しましたが、 JLS warns against this

初期化コードが無制限であるという事実は、それはまだその初期デフォルト値を有する場合、その初期化 式が評価される前に、クラス変数の値は、 を観察することができる場合 に例を構成することが可能となるが、そのような例は実際にはまれです。 (そのような の例は、変数の初期化 (§12.5)のために構成することもできます)。Javaプログラミング言語のフルパワーは、これらのイニシャライザでは です。 プログラマはケアをしなければならない


あなたの元Foo例がchapter on Enum Body Declarationsで定義された余分なルールを紹介します。

フィールドは 定数変数でなければ§(コンストラクタ、インスタンス初期化子、または列挙型のインスタンス変数 初期化式から列挙型 の静的フィールドを参照するようにコンパイル時エラーであります4.12.4)。

このルールは、お客様のFooスニペットのコンパイルをブロックします。

enum constants translate to public static final fields。これらは、最初にenumタイプ定義のテキストとして表示されるため、最初に初期化されます。初期化にはコンストラクタが必要です。このルールは、後で必ず初期化される他のクラス変数の初期化されていない値をコンストラクタが見られないようにするために存在します。

3

入れ子になったクラスの位置が意味を持たないために以下のコードを参照してください。メソッドが宣言される前に参照できるのと同じように、ネストされたクラスが宣言される前(ソーステキストの前)に参照することができます。

これは、ネストされたクラスが実際には独立したクラスであるためです。包含クラスと入れ子クラスは独立して初期化されます。

BarBazも初期化されていないとします。

一部のコードにBarが必要な場合は、Barが初期化されます。 Bazは、がBazを参照していないため、その時点では初期化されません。

ただし、一部のコードにBaz(まだ初期化されていないもの)が必要な場合は、Baz初期化が開始されます。 ABazコンストラクターが実行を開始すると、Barはまだ初期化されません。最初の行にはBarが必要で、Barの初期化が開始されます。 Barfirst.add(description)ステートメントが実行される前に、初期化が正常に完了します。 Barは完全に初期化されているため、2番目のステートメントを実行することもできます。

ご覧のとおり、初期化順序の競合はありません。 Barは、Bazコンストラクタで使用するために完全に初期化され、したがって完全に使用可能になります。


一連のイベントを表示するには、私はいくつかのprint文を追加しました:あなたが見ることができるように

class Bar$Baz.A in construction... 
Bar initializing... 
Bar initialized 
class Bar$Baz.A constructed... 
class Bar$Baz.B in construction... 
class Bar$Baz.B constructed... 
Baz initializing... 
Baz initialized... 

public class Test { 
    public static void main(String[] args) { 
     Bar.Baz x = Bar.Baz.A; 
    } 
} 
class Bar { 
    static { System.out.println("Bar initializing..."); } 
    static List<String> first = new ArrayList<>(); 

    enum Baz { 
     A("Some string"), 
     B("Some other string"), 
     ; 
     static { System.out.println("Baz initializing..."); } 


     Baz(String description) { 
      System.out.println(getClass() + "." + name() + " in construction..."); 
      // Can access static fields from before the enum 
      first.add(description); 

      // Can access static fields from _after_ the enum 
      second.add(description); 
      System.out.println(getClass() + "." + name() + " constructed..."); 
     } 
     static { System.out.println("Baz initialized..."); } 
    } 

    static List<String> second = new ArrayList<>(); 
    static { System.out.println("Bar initialized"); } 
} 

出力は、Barを使用したら、初期化開始しますBazコンストラクタ内で、完全に初期化されますその時zed。したがって、ソーステキストの位置に関係なく、Bazが完全に利用可能です。

また、あなたは、コンストラクタから静的メンバを参照することができない理由はもちろんである、後の列挙型が構築されるまでBaz静的初期化子が実行されないことがわかります。

+2

関連:[オンデマンドホルダーイディオムの初期化](https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom) – charlie

関連する問題