Private enums and static fields in the enclosing class
I understand why an enum constructor cannot access static fields and methods within the enum itself, and why the same is allowed for in classes. Se for an 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
.
Relevant background
The enum 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 because static fields are initialized in textual order (section 12.4.2)
Next, execute either the class variable initializers and static initializers of the class, or the field initializers of the interface, in textual order , as though they were a single block.
(emphasis mine)
and since the enum values themselves always precede any other fields, including static fields, they are not available to the enum constructor, ie no static fields may preceed the enum values A
, and B
.
Question
However, and here is my question, why is it that a "private" (enclosed inside a class) enum
can access static fields of its enclosing class, regardless of whether or not the enum appears before --- or --- after the static fields? In particular, where in the Java-specification is this specified?
See the below code 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<>();
}
This is a bit all over the place in the JLS. The When Initialization Occurs chapter states
The intent is that a class or interface type has a set of initializers that put it in a consistent state, and that this state is the first state 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 textually after the use, even though these class variables are in scope (§8.3.3). This restriction is designed to detect, at compile time, most circular or otherwise malformed initializations.
That bold snippet refers to the class directly containing the access.
enum
types are defined in the Java Language Specification, here
An enum declaration specifies a new enum type, a special kind of class type.
The fields 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 Baz
. The access is therefore allowed.
We can go even deeper into the detailed class initialization procedure, which explains that each class (class, interface, enum) is initialized independently. However, we can create an example that sees yet-to-be-initialized values
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);
}
}
}
This will print null
. The access to max
is allowed in the enum
type's constructor although our program execution reached the access before max
was initialized. The JLS warns against this
The fact that initialization code is unrestricted allows examples to be constructed where the value of a class variable can be observed when it still has its initial default value, before its initializing expression is evaluated, but such examples are rare in practice. (Such examples can be also constructed for instance variable initialization (§12.5).) The full power of the Java programming language is available in these initializers; programmers must exercise some care .
Your original Foo
example introduces an extra rule, defined in the chapter on Enum Body Declarations .
It is a compile-time error to reference a static field of an enum type from constructors, instance initializers, or instance variable initializer expressions of the enum type, unless the field is a constant variable (§4.12.4).
That rule blocks your Foo
snippet from compiling.
enum
constants translate to public static final
fields. These appear textually first in the enum
type definition and are therefore initialized first. Their initialization involves the constructor. The rules exists to prevent the constructor from seeing uninitialized values of other class variables that will necessarily be initialized later.
The position of nested classes has no meaning. You can refer to the nested class before (earlier in source text) it is declared, same as you can refer to methods before they are declared.
This is because the nested class is actually an independent class. The enclosing class and the nested class are independently initialized.
So, lets say the neither Bar
nor Baz
are initialized.
If some code needs Bar
then Bar
will be initialized. Baz
will not be initialized at that time, since Bar
does not refer to Baz
.
If however some code needs Baz
(none of them initialized yet), then Baz
initialization begins. When the Baz
constructor for A
begins running, Bar
is still not initialized. The first line then needs Bar
, and Bar
initialization begins. Bar
initialization completes normally, in full, before the first.add(description)
statement is executed. The second statement can also be executed, because Bar
is fully initialized.
As you can see, there is no initialization order conflict. Bar
will be fully initialized, and therefore fully available, for use by the Baz
constructor.
To see the sequence of events, I added some 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"); }
}
Output
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 starting initializing once used inside the Baz
constructor, and it will be completely initialized at that time. Therefore it is fully available for Baz
to use, regardless of source text position.
You can also see that Baz
static initializers don't run until after the enums have been constructed, which is of course why you can't reference static members from the constructor.
上一篇: 枚举元素之前的静态初始化
下一篇: 封闭类中的私有枚举和静态字段