Por que o construtor do enum não pode acessar campos estáticos?

Por que o construtor do enum não pode acessar campos e methods estáticos? Isso é perfeitamente válido com uma class, mas não é permitido com um enum.

O que estou tentando fazer é armazenar minhas instâncias de enum em um mapa estático. Considere este código de exemplo que permite a consulta por abbreivação:

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 ABBREV_MAP = new HashMap(); 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); } } 

Isso não funcionará porque o enum não permite referências estáticas em seu construtor. No entanto funciona apenas encontrar se implementado como uma 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 } 

O construtor é chamado antes de todos os campos estáticos terem sido inicializados, porque os campos estáticos (incluindo aqueles que representam os valores enum) são inicializados em ordem textual e os valores enum sempre vêm antes dos outros campos. Note que no seu exemplo de class você não mostrou onde ABBREV_MAP foi inicializado – se for depois de DOMINGO, você receberá uma exceção quando a class for inicializada.

Sim, é um pouco doloroso e provavelmente poderia ter sido melhor projetado.

No entanto, a resposta usual na minha experiência é ter um static {} bloco static {} no final de todos os inicializadores estáticos e fazer toda a boot estática lá, usando EnumSet.allOf para obter todos os valores.

Citação de JLS, seção “Enum Body Declarations” :

Sem essa regra, o código aparentemente razoável falharia no tempo de execução devido à circularidade de boot inerente aos tipos de enumeração. (Existe uma circularidade em qualquer class com um campo estático “auto-typescript”.) Aqui está um exemplo do tipo de código que falharia:

 enum Color { RED, GREEN, BLUE; static final Map colorMap = new HashMap(); Color() { colorMap.put(toString(), this); } } 

A boot estática desse tipo enum lançaria um NullPointerException porque a variável estática colorMap não é inicializada quando os construtores das constantes enum são executados. A restrição acima garante que esse código não seja compilado.

Observe que o exemplo pode ser facilmente refatorado para funcionar corretamente:

 enum Color { RED, GREEN, BLUE; static final Map colorMap = new HashMap(); static { for (Color c : Color.values()) colorMap.put(c.toString(), c); } } 

A versão refatorada está claramente correta, pois a boot estática ocorre de cima para baixo.

talvez seja isso que você quer

 public enum Day { Sunday("Sun"), Monday("Mon"), Tuesday("Tue"), Wednesday("Wed"), Thursday("Thu"), Friday("Fri"), Saturday("Sat"); private static final Map ELEMENTS; static { Map elements = new HashMap(); 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); } } 

O problema resolvido através de uma class aninhada. Prós: é mais curto e também melhor pelo consumo de CPU. Contras: mais uma class na memory da JVM.

 enum Day { private static final class Helper { static Map 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); } 

Quando uma class é carregada na JVM, os campos estáticos são inicializados na ordem em que aparecem no código. Por exemplo

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

A saída será 0. Observe que a boot do test4 ocorre no processo de boot estática e, durante esse tempo, j ainda não foi inicializado como aparece posteriormente. Agora, se mudarmos a ordem dos inicializadores estáticos, tal que j vem antes do test4. A saída será 6.Mas no caso de Enums, não podemos alterar a ordem dos campos estáticos. A primeira coisa no enum deve ser as constantes que são realmente instâncias finais estáticas do tipo enum.Assim, para enums, é sempre garantido que os campos estáticos não serão inicializados antes das constantes enum.Desde não podermos fornecer nenhum valor sensato aos campos estáticos para uso no construtor enum , seria sem sentido acessá-los no construtor enum.