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
.
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, i.e. no static fields may preceed the enum
values A
, and B
.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With