Kevin Krumwiede
Kevin Krumwiede

Reputation: 10288

Why does this enum compile?

I wanted to create an enum where each constant has a Map associated with it. I accomplished this by giving each constant an instance initializer, like so:

import java.util.HashMap;
import java.util.Map;

public enum Derp {
    FOO {{
            mMap.put("bar", 1);
        }};

    // cannot be private
    protected final Map<String, Integer> mMap = new HashMap<>();
}

I found that if mMap is private, it cannot be referenced in the instance initializer. The error is Cannot make a static reference to the non-static field mMap. Before the reason for this occurred to me, I consulted JLS §8.9.2, which says in part:

It is a compile-time error for the constructors, instance initializer blocks, or instance variable initializer expressions of an enum constant e to refer to e or to an enum constant of the same type that is declared to the right of e.

Aren't I breaking this rule by implicitly referencing FOO in FOO's own instance intializer? How does this compile? It not only compiles, but works correctly at runtime.

(It occurred to me that mMap cannot be private because I'm implicitly creating an anonymous subclass which cannot reference a private field in its superclass. Which is a bit weird in itself since enums are implicitly final...)

Upvotes: 14

Views: 311

Answers (2)

Radiodef
Radiodef

Reputation: 37845

It is a compile-time error for the constructors, instance initializer blocks, or instance variable initializer expressions of an enum constant e to refer to e or to an enum constant of the same type that is declared to the right of e.

The specification here just means you can't reference by name because the field referred to by e is not initialized yet. It doesn't mean you can't access this.

It's basically the same rule as any other initializer (like int x = x;).

We can see why with an example like (Ideone):

enum Example {
    INSTANCE {{
        subversion();
    }};

    static void subversion() {
        System.out.println(INSTANCE);
    }

    public static void main(String[] args) {
        System.out.println(INSTANCE);
    }
}

Which outputs

null
INSTANCE

I found that if mMap is private, it cannot be referenced in the instance initializer.

You can qualify the call as super.mMap.put(...);. The private mMap is not inherited, but it's accessible from the inner class. I also covered this here. The short of it is that the simple name mMap refers to a non-existent outer instance of Derp.

We can verify this is the case with an example like (Ideone):

class Example {
    private int x;

    class Inner extends Example {{
        x = 1;       // refers to the outer instance
        super.x = 2; // refers to the inner instance
    }}

    public static void main(String[] args) {
        Example outer = new Example();
        Example inner = outer.new Inner();
        System.out.println(outer.x); // prints 1
        System.out.println(inner.x); // prints 2
    }
}

Except in your case FOO is static so there is no outer instance—hence compiler error.

Upvotes: 4

Raniz
Raniz

Reputation: 11113

It's because FOO is it's own anonymous subclass of Derp - which already exists when FOO is created.

public class Enums {
    public enum Derp {
        FOO {{
            mMap.put("bar", 1);
        }};

        // cannot be private
        protected final Map<String, Integer> mMap = new HashMap<>();
    }

    public static void main(String[] args) {
        System.out.println(Derp.class);
        System.out.println(Derp.FOO.getClass());
    }
}

class Enums$Derp
class Enums$Derp$1

Upvotes: 1

Related Questions