129

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
}
1

7 Answers 7

129

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.

9
  • 48
    If you add a nested class, then the statics of that will be initialised at an appropriate time. Commented Jan 14, 2009 at 18:53
  • Ooh, nice one. I hadn't thought of that.
    – Jon Skeet
    Commented Jan 14, 2009 at 19:48
  • 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! Commented Jul 7, 2011 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
    Commented Oct 13, 2012 at 13:51
  • 1
    @Chirlo There is a question 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
    Commented Jun 21, 2016 at 1:27
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.

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);
    }
}
2
  • 4
    Using Collections.unmodifiableMap() is a very good practice here. +1
    – 4castle
    Commented Jun 21, 2016 at 0:50
  • 1
    Exactly what I was looking for. I also like seeing Collections.unmodifiableMap. Thank you!
    – LethalLima
    Commented Dec 24, 2019 at 18:44
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);
    }
0
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.

0

This is a workaround (that as of now has not yet been suggested) to the problem of using static variables to call an enum constructor.

Use a statically initiated map and call the enum constructor with the String keys to that map.

enum MyEnum {

    ONE('KEY_1'),
    TWO('KEY_2')

    private static final Map<String, Double> PRIVATE_MAP

    static {
        double someUsefulCalculationInAStaticContextToInitializeTheMap = 0.8
        PRIVATE_MAP = Collections.unmodifiableMap([
                KEY_1: someUsefulCalculationInAStaticContextToInitializeTheMap * 0.20,
                KEY_2: someUsefulCalculationInAStaticContextToInitializeTheMap * 1.2
        ])
    }

    private final String mapKey

    private MyEnum(String mapKey) {
        this.mapKey = mapKey
    }

    Double getMyEnumProperty() { PRIVATE_MAP[mapKey] }
}
0

Instead of populating the map in the constructor you could do that in a static block. Or since Java 8 you could use Stream and Lambdas like this

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

  private final String abbreviation;

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

  public String getAbbreviation() {
    return abbreviation;
  }

  private static final Map<String, Day> ABBREV_MAP = Stream.of(values()).collect(Collectors.toMap(e -> e.abbreviation, e -> e));

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.