Java中从实例初始化器更新static final字段为何受限及如何解决?
为什么Java禁止从初始化器访问final static字段?
刚好之前处理过类似的枚举初始化问题,来给你捋一捋为什么Java会禁止这种操作,以及怎么解决~
首先看你给出的示例代码,在枚举的构造器里尝试往final static的Map中添加当前实例:
public enum Example { ValueA("valueAA", "valueAB"), ValueB("valueBA", "valueBB"); final static Map<String, Example> exampleByPropertyA = new HashMap<>(); final String propertyA; final String propertyB; Example(String propertyA, String propertyB) { this.propertyA = propertyA; this.propertyB = propertyB; Example.exampleByPropertyA.put(propertyA, this); // <- 这里会触发问题 } }
为什么这会被禁止?
这本质是Java类初始化顺序导致的问题:
- 当JVM加载枚举类时,会先初始化所有
static字段,包括你定义的exampleByPropertyA。 - 紧接着,JVM会创建所有枚举实例(也就是
ValueA和ValueB),调用它们的构造器。 - 但问题在于,
final static的Map虽然已经被初始化(new HashMap<>()),但JVM的类初始化机制会认为:在实例构造过程中访问final static字段,可能会导致字段处于未完全就绪的状态(尤其是如果final static字段的初始化依赖于实例的话,会形成循环依赖)。所以JVM会直接阻止这种操作,运行时可能抛出IllegalStateException或者直接导致类初始化失败。
怎么解决这个问题?
有两种常用的方案,都能避开初始化顺序的冲突:
方案一:使用静态代码块初始化Map
静态代码块会在所有枚举实例都创建完成之后执行,此时可以安全地遍历所有实例并填充Map:
import java.util.Collections; import java.util.HashMap; import java.util.Map; public enum Example { ValueA("valueAA", "valueAB"), ValueB("valueBA", "valueBB"); private static final Map<String, Example> exampleByPropertyA; final String propertyA; final String propertyB; Example(String propertyA, String propertyB) { this.propertyA = propertyA; this.propertyB = propertyB; } // 静态代码块在所有实例初始化完成后执行 static { exampleByPropertyA = new HashMap<>(); for (Example example : Example.values()) { exampleByPropertyA.put(example.propertyA, example); } // 可选:把Map设置为不可变,避免后续被修改 exampleByPropertyA = Collections.unmodifiableMap(exampleByPropertyA); } // 提供获取方法 public static Example getByPropertyA(String propertyA) { return exampleByPropertyA.get(propertyA); } }
方案二:懒加载Map(延迟初始化)
如果这个Map不经常被用到,可以用懒加载的方式,在第一次调用获取方法时再初始化Map,这样既避免了类初始化时的开销,也避开了初始化顺序的问题:
import java.util.Collections; import java.util.HashMap; import java.util.Map; public enum Example { ValueA("valueAA", "valueAB"), ValueB("valueBA", "valueBB"); private static volatile Map<String, Example> exampleByPropertyA; final String propertyA; final String propertyB; Example(String propertyA, String propertyB) { this.propertyA = propertyA; this.propertyB = propertyB; } public static Example getByPropertyA(String propertyA) { if (exampleByPropertyA == null) { // 双重检查锁保证线程安全 synchronized (Example.class) { if (exampleByPropertyA == null) { Map<String, Example> tempMap = new HashMap<>(); for (Example example : Example.values()) { tempMap.put(example.propertyA, example); } exampleByPropertyA = Collections.unmodifiableMap(tempMap); } } } return exampleByPropertyA.get(propertyA); } }
这两种方案都能完美解决你遇到的问题,根据你的实际场景选择就好~
内容的提问来源于stack exchange,提问作者Andy Senn
相关产品推荐
相关产品推荐

