2016-08-17 34 views
7

Ich verstehe, warum ein Enum-Konstruktor nicht auf statische Felder und Methoden innerhalb der Enumeration selbst zugreifen kann, und warum das gleiche für für in Klassen zulässig ist. Du für ein Beispiel des folgenden Code,Private Enums und statische Felder in der umschließenden Klasse

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); 
    } 
} 

Dieser Code führt zu einem Fehler bei der Kompilierung, illegal reference to static field from initializer.

Relevante Hintergrund

Der Enum-Konstruktor aufgerufen wird, bevor die statische Felder haben alle initialisiert. Im obigen Beispiel bedeutet dies, dass list noch nicht initialisiert ist. Dies liegt daran, statische Felder in Text Reihenfolge (section 12.4.2)

Als nächstes ausführen entweder die Klassen Variableninitialisierungen und statische initializers der Klasse initialisiert werden, oder das Feld initializers die Schnittstelle, in textlichen Reihenfolge, wie obwohl sie ein einzelner Block waren.

(Hervorhebung von mir)

und da die ENUM-Werte sich immer alle anderen Felder vorausgehen, einschließlich der statischen Felder, sie sind an den Enum-Konstruktor nicht verfügbar, dh es werden keine statischen Felder der Enum vorausgehen kann Werte A und B.

Frage

Allerdings, und hier ist meine Frage, warum ist es, dass ein „privater“ (innerhalb einer Klasse eingeschlossen) enum statische Felder der umgebenden Klasse zugreifen kann, unabhängig davon, ob die Enum erscheint vor --- oder --- nach den statischen Feldern? Insbesondere wo ist das in der Java-Spezifikation spezifiziert?

Siehe unten stehenden Code als Referenz

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

Interessante Frage. Ich bin mir nicht sicher, ob die Annahme, dass der Konstruktor vor der Initialisierung statischer Felder aufgerufen wird, korrekt ist. Ich denke, es ist eher das Gegenteil. Sortierung: http://stackoverflow.com/questions/36407743/enum-based-table-matrix-in-java –

+0

@SotiriosDelimanolis eigentlich die Liste der Enum-Konstanten ist mehr oder weniger eine Liste der Objekte, die mit dem Enum-Konstruktor erstellt wurden. –

+0

@SotiriosDelimanolis Beispiele wurden zur Verfügung gestellt, danke. Und Entschuldigung. –

Antwort

4

Dies ist ein bisschen überall in der JLS. Die When Initialization Occurs chapter Staaten

Die Absicht ist, dass eine Klasse oder Interface-Typ eine Reihe von Initialisierungen hat, dass es in einem konsistenten Zustand versetzt, und dass dieser Zustand ist der erste Zustand, die von anderen Klassen beobachtet wird. Die static initializers und Klasse Variableninitialisierungen werden in Text Reihenfolge ausgeführt, und beziehen sich möglicherweise nicht Variablen Klasse deklariert in der Klasse deren Erklärungen erscheinen textlich nach dem Gebrauch, obwohl diese Klassenvariablen sind in Rahmen (§8.3. 3). Diese Einschränkung dient dazu, bei der Kompilierung die meisten zirkulären oder anderweitig fehlerhaften Initialisierungen zu erkennen.

Dieses fett formatierte Snippet bezieht sich auf die Klasse, die den Zugriff direkt enthält.

enum Typen sind in der Java Language Specification definiert, here

eine ENUM-Deklaration gibt einen neuen Aufzählungstyp, eine besondere Art von Klassentyp.

Die Felder gelangen Sie in den Baz Konstruktor

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); 
} 

sind nicht Klassenvariablen in der Klasse deklariert, der Aufzählungstyp Baz. Der Zugriff ist daher erlaubt.

Wir können noch tiefer in die detailed class initialization procedure gehen, die erklärt, dass jede Klasse (Klasse, Schnittstelle, enum) unabhängig initialisiert wird. Allerdings können wir ein Beispiel erstellen, die noch-zu-sein-initialisierten Werte

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); 
     } 
    } 
} 

Dies druckt null sieht. Der Zugriff auf max ist im Konstruktor enum zulässig, obwohl unsere Programmausführung den Zugriff erreicht hat, bevor max initialisiert wurde. Die JLS warns against this

Die Tatsache, dass Initialisierungscode unbeschränkten ist erlaubt Beispiele aufgebaut werden, in dem der Wert einer Klassenvariablen kann beobachtet werden, wenn es immer noch seinen ursprünglichen Standardwert hat, vor seiner Initialisierung Ausdrucks ausgewertet wird, aber Solche Beispiele sind in der Praxis selten. (Solche Beispiele können auch zum Beispiel zur Variableninitialisierung (§12.5) konstruiert werden.) Die volle Leistung der Java-Programmiersprache ist in diesen Initialisierern verfügbar ; Programmierer müssen etwas Sorgfalt ausüben.


Ihr ursprüngliches Foo Beispiel stellt eine zusätzliche Regel, in den chapter on Enum Body Declarations definiert.

Es ist ein Fehler Übersetzungszeit ein statisches Feld eines ENUM-Typ von Konstrukteuren, Instanz Initialisierungen oder Instanzvariable Initialisierer Ausdrücke der Aufzählungstyp zu verweisen, es sei denn, das Feld ein konstante Variable (§ ist 4.12.4).

Diese Regel blockiert Ihr Foo Snippet vom Kompilieren.

enum constants translate to public static final fields. Diese erscheinen textuell zuerst in der Typdefinition enum und werden daher zuerst initialisiert. Ihre Initialisierung beinhaltet den Konstruktor. Die Regeln existieren, um zu verhindern, dass der Konstruktor nicht initialisierte Werte anderer Klassenvariablen sieht, die später notwendigerweise initialisiert werden.

3

Die Position von verschachtelten Klassen keine Bedeutung hat. Sie können auf die verschachtelte Klasse verweisen, bevor (früher im Quelltext) sie deklariert wird, genauso wie Sie sich auf Methoden beziehen können, bevor sie deklariert werden.

Dies liegt daran, die verschachtelte Klasse tatsächlich eine unabhängige Klasse. Die einschließende Klasse und die verschachtelte Klasse werden unabhängig voneinander initialisiert.

Also sagen wir, dass weder Bar noch Baz initialisiert werden.

Wenn ein Code Bar benötigt, wird Bar initialisiert. Baz wird zu diesem Zeitpunkt nicht initialisiert werden, da Bar nicht Baz bezieht.

Wenn jedoch ein Code Baz (noch keiner davon initialisiert) benötigt, beginnt die Initialisierung Baz. Wenn der Baz-Konstruktor für A gestartet wird, wird Bar noch nicht initialisiert.Die erste Zeile benötigt dann Bar und Bar Initialisierung beginnt. Bar Die Initialisierung wird normalerweise vollständig abgeschlossen, bevor die Anweisung first.add(description) ausgeführt wird. Die zweite Anweisung kann auch ausgeführt werden, weil Bar vollständig initialisiert ist.

Wie Sie sehen können, gibt es keinen Konflikt bei der Initialisierungsreihenfolge. Bar wird vollständig initialisiert und ist daher vollständig verfügbar für den Baz-Konstruktor.


die Abfolge der Ereignisse zu sehen, habe ich einige print-Anweisungen hinzugefügt:

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"); } 
} 

Ausgabe

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... 

Wie Sie sehen können, wird Bar einmal verwendet Initialisierung starten innerhalb des Baz Konstruktors, und es wird vollständig initiali sein Zed zu dieser Zeit. Daher ist es uneingeschränkt für Baz zu verwenden, unabhängig von der Quelltextposition.

Sie können auch sehen, dass Baz statische Initialisierer laufen nicht bis nach die Aufzählungen gebaut worden, was natürlich ist, warum Sie nicht statisch Mitglieder aus dem Konstruktor verweisen können.

+2

Related: [Initialisierung-auf-Anfrage Halter Idiom] (https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom) – charlie