Why can't enum's constructor access static fields?

JavaEnums

Java Problem Overview


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
}

Java Solutions


Solution 1 - Java

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.

Solution 2 - Java

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 colorMap = new HashMap(); >
> 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 colorMap = new HashMap(); >
> static { > for (Color c : Color.values()) > colorMap.put(c.toString(), c); > } > } The refactored version is clearly correct, as static initialization occurs top to bottom.

Solution 3 - Java

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);
    }
}

Solution 4 - Java

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);
    }

Solution 5 - Java

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSteve KuoView Question on Stackoverflow
Solution 1 - JavaJon SkeetView Answer on Stackoverflow
Solution 2 - JavaPhaniView Answer on Stackoverflow
Solution 3 - Javauser4767902View Answer on Stackoverflow
Solution 4 - JavaPavel VlasovView Answer on Stackoverflow
Solution 5 - JavaHiteshView Answer on Stackoverflow