认为静态块只能访问静态字段是一种常见的误解。为此,我想在下面展示我在实际项目中经常使用的一段代码(部分复制自another answer,但上下文略有不同):
public enum Language {
ENGLISH("eng", "en", "en_GB", "en_US"),
GERMAN("de", "ge"),
CROATIAN("hr", "cro"),
RUSSIAN("ru"),
BELGIAN("be",";-)");
static final private Map<String,Language> ALIAS_MAP = new HashMap<String,Language>();
static {
for (Language l:Language.values()) {
// ignoring the case by normalizing to uppercase
ALIAS_MAP.put(l.name().toUpperCase(),l);
for (String alias:l.aliases) ALIAS_MAP.put(alias.toUpperCase(),l);
}
}
static public boolean has(String value) {
// ignoring the case by normalizing to uppercase
return ALIAS_MAP.containsKey(value.toUpper());
}
static public Language fromString(String value) {
if (value == null) throw new NullPointerException("alias null");
Language l = ALIAS_MAP.get(value);
if (l == null) throw new IllegalArgumentException("Not an alias: "+value);
return l;
}
private List<String> aliases;
private Language(String... aliases) {
this.aliases = Arrays.asList(aliases);
}
}
这里初始化器用于维护一个索引(ALIAS_MAP),将一组别名映射回原始枚举类型。它旨在作为 Enum 本身提供的内置 valueOf 方法的扩展。
如您所见,静态初始化程序甚至访问private 字段aliases。重要的是要了解static 块已经可以访问Enum 值实例(例如ENGLISH)。这是因为order of initialization and execution in the case of Enum types,就像static private 字段在调用static 块之前已经用实例初始化:
-
Enum 常量是隐式静态字段。这需要 Enum 构造函数和实例块,以及实例初始化首先发生。
-
static 按出现顺序阻止和初始化静态字段。
这种无序初始化(static 块之前的构造函数)需要注意。当我们使用类似于 Singleton 的实例初始化静态字段时也会发生这种情况(进行了简化):
public class Foo {
static { System.out.println("Static Block 1"); }
public static final Foo FOO = new Foo();
static { System.out.println("Static Block 2"); }
public Foo() { System.out.println("Constructor"); }
static public void main(String p[]) {
System.out.println("In Main");
new Foo();
}
}
我们看到的是以下输出:
Static Block 1
Constructor
Static Block 2
In Main
Constructor
很明显,静态初始化实际上可以发生在构造函数之前,甚至之后:
只需在 main 方法中访问 Foo,就会加载类并开始静态初始化。但是作为静态初始化的一部分,我们再次调用静态字段的构造函数,之后它恢复静态初始化,并完成从 main 方法中调用的构造函数。相当复杂的情况,我希望在正常编码中我们不必处理。
有关这方面的更多信息,请参阅“Effective Java”一书。