125

Why can't enum's constructor access static fields and methods? This is perfectly valid with a class, but is not allowed with an enum.

What I'm trying to do is store my enum instances in a static Map. Consider this example code which allows lookup by abbreivation:

public enum Day {
    Sunday("Sun"), Monday("Mon"), Tuesday("Tue"), Wednesday("Wed"), Thursday("Thu"), Friday("Fri"), Saturday("Sat");

    private final String abbreviation;

    private static final Map<String, Day> ABBREV_MAP = new HashMap<String, Day>();

    private Day(String abbreviation) {
        this.abbreviation = abbreviation;
        ABBREV_MAP.put(abbreviation, this);  // Not valid
    }

    public String getAbbreviation() {
        return abbreviation;
    }

    public static Day getByAbbreviation(String abbreviation) {
        return ABBREV_MAP.get(abbreviation);
    }
}

This will not work as enum doesn't allow static references in its constructor. It however works just find if implemented as a class:

public static final Day SUNDAY = new Day("Sunday", "Sun");
private Day(String name, String abbreviation) {
    this.name = name;
    this.abbreviation = abbreviation;
    ABBREV_MAP.put(abbreviation, this);  // Valid
}
skaffman
  • 398,947
  • 96
  • 818
  • 769
Steve Kuo
  • 61,876
  • 75
  • 195
  • 257
  • As describe by https://stackoverflow.com/questions/443980/why-cant-enums-constructor-access-static-fields#comment265704_444000, we have this workaround Day(String... abbreviations) { For.ABBR.put(name(), this); for (String abbr : abbreviations) { For.ABBR.put(abbr, this); } } // workaround https://stackoverflow.com/questions/443980/why-cant-enums-constructor-access-static-fields private static class For { private static final Map ABBR = new HashMap<>(); } – William Leung Oct 23 '20 at 07:32

5 Answers5

127

The constructor is called before the static fields have all been initialized, because the static fields (including those representing the enum values) are initialized in textual order, and the enum values always come before the other fields. Note that in your class example you haven't shown where ABBREV_MAP is initialized - if it's after SUNDAY, you'll get an exception when the class is initialized.

Yes, it's a bit of a pain and could probably have been designed better.

However, the usual answer in my experience is to have a static {} block at the end of all the static initializers, and do all static initialization there, using EnumSet.allOf to get at all the values.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 47
    If you add a nested class, then the statics of that will be initialised at an appropriate time. – Tom Hawtin - tackline Jan 14 '09 at 18:53
  • Ooh, nice one. I hadn't thought of that. – Jon Skeet Jan 14 '09 at 19:48
  • I don't really understand the statement about the static initializer block. I tried creating a static initializer block in an enum class, and it was being called _after_ the constructor of the enum member, apparently done according to textual order as you said. – Hosam Aly Feb 11 '09 at 21:16
  • If you try to use a static variable within the enum constructor (as in the question) you'll see the uninitialized value. Instead, use a static initializer to initialize everything *after* the enum values have all been constructed and the rest of the static variables are initialized. – Jon Skeet Feb 11 '09 at 22:01
  • 4
    Bit of an odd one but if you call a static method in an enum constructor which returns a static value it will compile fine - but the value it returns will be the default one for that type (i.e. 0, 0.0, '\u0000' or null), even if you explicitly set it (unless it's declared as `final`). Guess that'll be a difficult one to catch! – Mark Rhodes Jul 07 '11 at 14:58
  • 2
    quick spin-off question @JonSkeet: Any reason that you use `EnumSet.allOf` instead of `Enum.values()`? I ask because `values` is kind of a phantom method (can't see the source in `Enum.class`) and i don't know when its created – Chirlo Oct 13 '12 at 13:51
  • @TomHawtin-tackline - That's about the only thing that works if you want to use `static final` fields as arguments to an `enum` constructor. (Of course, it doesn't have to be a nested class; you just have to remove the statics from the `enum` itself into another class somewhere.) – Ted Hopp Nov 24 '15 at 19:19
  • 1
    @Chirlo There is a [question](http://stackoverflow.com/q/2464950/5743988) about that. It seems that `Enum.values()` is faster if you plan on iterating over them with an enhanced for loop (since it returns an array), but mostly its about style and use case. It's probably better to use `EnumSet.allOf()` if you want to write code which exists in Java's documentation instead of just in the specs, but many people seem to be familiar with `Enum.values()` anyway. – 4castle Jun 21 '16 at 01:27
  • jfyi doing it in groovy you also see this issue – diarmuid Nov 24 '17 at 10:04
43

Quote from JLS, section "Enum Body Declarations":

Without this rule, apparently reasonable code would fail at run time due to the initialization circularity inherent in enum types. (A circularity exists in any class with a "self-typed" static field.) Here is an example of the sort of code that would fail:

enum Color {
    RED, GREEN, BLUE;
    static final Map<String,Color> colorMap = new HashMap<String,Color>();

    Color() {
       colorMap.put(toString(), this);
    }
}

Static initialization of this enum type would throw a NullPointerException because the static variable colorMap is uninitialized when the constructors for the enum constants run. The restriction above ensures that such code won’t compile.

Note that the example can easily be refactored to work properly:

enum Color {
    RED, GREEN, BLUE;
    static final Map<String,Color> colorMap = new HashMap<String,Color>();

    static {
        for (Color c : Color.values())
            colorMap.put(c.toString(), c);
    }
}

The refactored version is clearly correct, as static initialization occurs top to bottom.

informatik01
  • 16,038
  • 10
  • 74
  • 104
Phani
  • 5,319
  • 6
  • 35
  • 43
12

maybe this is what you want

public enum Day {
    Sunday("Sun"), 
    Monday("Mon"), 
    Tuesday("Tue"), 
    Wednesday("Wed"), 
    Thursday("Thu"), 
    Friday("Fri"), 
    Saturday("Sat");

    private static final Map<String, Day> ELEMENTS;

    static {
        Map<String, Day> elements = new HashMap<String, Day>();
        for (Day value : values()) {
            elements.put(value.element(), value);
        }
        ELEMENTS = Collections.unmodifiableMap(elements);
    }

    private final String abbr;

    Day(String abbr) {
        this.abbr = abbr;
    }

    public String element() {
        return this.abbr;
    }

    public static Day elementOf(String abbr) {
        return ELEMENTS.get(abbr);
    }
}
user4767902
  • 121
  • 1
  • 5
9

The problem solved via a nested class. Pros: it's shorter and also better by CPU consumption. Cons: one more class in JVM memory.

enum Day {

    private static final class Helper {
        static Map<String,Day> ABBR_TO_ENUM = new HashMap<>();
    }

    Day(String abbr) {
        this.abbr = abbr;
        Helper.ABBR_TO_ENUM.put(abbr, this);

    }

    public static Day getByAbbreviation(String abbr) {
        return Helper.ABBR_TO_ENUM.get(abbr);
    }
Pavel Vlasov
  • 4,206
  • 6
  • 41
  • 54
1

When a class is loaded in the JVM then static fields are initialized in the order in which they appear in code. For e.g.

public class Test4 {
        private static final Test4 test4 = new Test4();
        private static int j = 6;
        Test4() {
            System.out.println(j);
        }
        private static void test() {
        }
        public static void main(String[] args) {
            Test4.test();
        }
    }

The output will be 0. Note that test4 initialization takes place in static initialization process and during this time j is not yet initialized as it appears later. Now if we switch order of static initializers such that j comes before test4. The output will be 6.But in case of Enums we cannot alter order of static fields. The first thing in enum must be the constants which are actually static final instances of enum type.Thus for enums its always guaranteed that static fields wont be initialized before enum constants.Since we cannot give any sensible values to static fields for use in enum constructor, it would be meaningless to access them in enum constructor.

Hitesh
  • 703
  • 1
  • 9
  • 14