Private enumerations and static fields in a closing class

I understand why the enumeration constructor cannot access the static fields and methods inside the enumeration itself, and why the same thing is allowed for classes. Se for example the following code,

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

This code causes a compile-time error, illegal reference to static field from initializer .

Matching background

The enumeration constructor is called before the static fields have all been initialized. In the above example, this means that list is not yet initialized. This is due to the fact that static fields are initialized in text (section 12.4.2)

Next, execute either class variable initializers, or static class initializers or interface field initializers in textual order , as if they were a single block.

(my accent)

and since enumeration values ​​themselves always precede other fields, including static fields, they are not accessible to enum, i.e. no static fields can precede the enumeration of the values ​​of A and B

Question

However, and here is my question, why is there a "private" (enclosed within the class) enum can access the static fields of its enclosing class, regardless of whether the enumeration appears before --- or --- after the static fields? In particular, where is the Java specification indicated?

See the code below for reference.

 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<>(); } 
+7
java enums static
source share
2 answers

This is a bit everywhere in JLS. Upon initialization, the chapter asserts

The goal is that the class or type of interface has a set of initializers which puts it in a sequential state and that this state is the first that is observed by other classes. The static initializers and class variable initializers are executed in textual order and may not refer to class variables declared in the class whose declarations appear after use, although these class variables are in scope (§8.3.3). This restriction is intended to detect when compiling time, most circular or otherwise distorted initializations.

This bold fragment refers to a class that directly contains access.

enum types are defined in the Java Language Specification, here

The enum declaration indicates a new enumeration type, a special type of class type.

Fields that you access in the Baz constructor

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

are not class variables declared in the class , the enum type is Baz . Therefore, access is allowed.

We can go even deeper into the detailed class initialization procedure , which explains that each class (class, interface, enumeration) is initialized independently. However, we can create an example that sees values ​​not yet to be initialized.

 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 open. Access to max allowed in the constructor of the enum type, although our program execution reached access before initializing max . JLS warns about it

The fact that the initialization code is unlimited allows, where you can observe the value of a class variable when it still has an initial default value, before its initialization, the expression is evaluated, but such examples are rare in practice. (Such examples can also be constructed, for example, to initialize variables (§12.5).) The full power of the Java programming language is available in these initializers; programmers must exercise some caution .


The original Foo example introduces an additional rule defined in the chapter in Enum Body declarations .

This is a compile-time error for referencing a static field of an enumeration type from constructors, instance initializers, or instance variables of an expression of an initializer of an enumeration type if the field is not a constant variable (§4.12.4).

This rule blocks the compilation of your Foo fragment.

enum constants to translate into public static final fields . They are first displayed first in the enum type definition and are therefore initialized first. Their initialization includes a constructor. Rules exist to prevent the constructor from seeing uninitialized values ​​of other class variables that will necessarily be initialized later.

+4
source share

The position of nested classes does not make sense. You can reference a nested class before (earlier in the source text) that it is declared, just as you can reference methods before they are declared.

This is because a nested class is actually an independent class. The closing class and nested class are initialized independently.

So, let's say that neither Bar nor Baz are initialized.

If some code needs Bar , then Bar will be initialized. Baz will not initialize at this time, since Bar not related to Baz .

If any code needs Baz (none of them have yet been initialized), Baz initialization begins. When the Baz constructor for A starts to work, Bar is still not initialized. The first line requires Bar , and the initialization of Bar begins. Bar initialization completes normally, completely, before the first.add(description) statement is first.add(description) . The second statement can also be executed because Bar fully initialized.

As you can see, there is no conflict of initialization conflicts. Bar will be fully initialized and therefore fully accessible for use by the Baz constructor.


To see the sequence of events, I added several print statements:

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

Exit

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

As you can see, Bar will start initializing after being used inside the Baz constructor, and at that time it will be fully initialized. Therefore, it is fully available for use by Baz , regardless of the source code.

You can also see that Baz static initializers do not start until enums are created, which, of course, does not allow you to refer to static members from the constructor.

+3
source share

All Articles