Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Private enums and static fields in the enclosing class

Tags:

java

enums

static

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, i.e. 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<>();
}
like image 673
Filip Allberg Avatar asked Aug 17 '16 16:08

Filip Allberg


1 Answers

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.

like image 55
Sotirios Delimanolis Avatar answered Nov 12 '22 20:11

Sotirios Delimanolis