14

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

Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
Kevin Krumwiede
  • 9,868
  • 4
  • 34
  • 82
  • 1
    "enums are implicitly `final`" - [not quite](http://ideone.com/3bi0U1). Java needs to create implicit per-instance subclasses every time you do the thing with the braces next to an enum constant, so if you do that, the enum class isn't final. You still can't declare your own subclasses of it, though. – user2357112 Apr 15 '15 at 03:31
  • 1
    Qualify the call like `super.mMap.put(...);`. The reason behind this is just a weird specification. – Radiodef Apr 15 '15 at 03:32
  • @Radiodef I'm not convinced this is a duplicate. I think this poster is asking two questions that were not asked in the linked-to post: (1) why is it legal if he uses `protected` and (2) why isn't the implicit self-reference illegal. I looked over the answers there, and they don't answer the question to my satisfaction. – ajb Apr 15 '15 at 03:37
  • @ajb That's exactly what prompted the question. Not only does this compile, but it does not throw a NPE. `FOO.mMap` contains the correct values at runtime. – Kevin Krumwiede Apr 15 '15 at 03:37
  • sorry, I missed the part where you said it worked at runtime. – ajb Apr 15 '15 at 03:39
  • 2
    @ajb `protected` works because `protected` fields exist in the subclass, whereas the `private` fields don't (as taken from the other question). As for why it works at runtime, the `.. mMap = new HashMap<>()` line is pre-pended to the `` (constructor) code, so `mMap` is initialized before anything is ever put into it. – FThompson Apr 15 '15 at 03:41
  • The specification referenced here just means you can't reference by name e.g. `enum Foo { FOO{{ System.out.println(FOO); }} }` because it's not initialized yet. It doesn't mean you can't access `this`. It's basically the same rule as any other initializer e.g. `int x = x;` or `static final Object o = new Object() {{ System.out.println(o); }};`. – Radiodef Apr 15 '15 at 03:43
  • 2
    @Radiodef In other words, you can reference `this` but not `Derp.FOO` because `Derp.FOO` has not been assigned yet. Right? I think that's the answer, if you want to make it one. – Kevin Krumwiede Apr 15 '15 at 03:44
  • Question is closed so I can't answer but yes, that's right. `Derp.FOO` is null inside the initializer for `FOO`. http://ideone.com/SPf9zx I'll post an answer if it gets reopened, sorry. – Radiodef Apr 15 '15 at 03:47
  • 1
    I'd say the site is being ruined more by bad and careless questions, but unfortunately others who also don't like such questions can sometimes be too quick to close something as a duplicate. This isn't the worst example--I've seen many worse cases of questions closed by good StackOverflow citizens who see a couple words and overreact. Anyway, after doing a bunch of research, I've posted another answer over at the dupe question that might help clarify things (about the `private`/`protected`). Finally, no matter how much of a problem you think this is, we don't allow foul language on SO. – ajb Apr 15 '15 at 05:30
  • Well, now that it's been reopened, the link is gone... here's where I put my answer: http://stackoverflow.com/questions/28964926. – ajb Apr 15 '15 at 05:31

2 Answers2

4

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.

Community
  • 1
  • 1
Radiodef
  • 37,180
  • 14
  • 90
  • 125
1

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

Raniz
  • 10,882
  • 1
  • 32
  • 64