正如@Mureinik 所说,“总之——是的。这个代码是完全合法的。” 我想提供一个更彻底的答案,因为在将静态初始化程序与类方法结合使用时,您很容易出现问题——出现的顺序会影响类状态,而且这是一个令人讨厌的错误。
类中声明的静态初始化器在类初始化时执行。与类变量的任何字段初始化器一起......静态初始化器可用于初始化类的类变量——Java语言规范(JLS)§8.7
初始化通常按照出现的顺序进行(称为文本排序)。例如,考虑以下代码:
class Bar {
static int i = 1;
static {i += 1;}
static int j = i;
}
class Foo {
static int i = 1;
static int j = i;
static {i += 1;}
public static void main(String[] args) {
System.out.println("Foo.j = " + Foo.j);
System.out.println("Bar.j = " + Bar.j);
}
}
Foo.j
和的值Bar.j
不同,因为代码的文本顺序不同:
Foo.j = 1
Bar.j = 2
OP 的示例按文本顺序执行。但是如果代码被重新排列,比如说,以相反的顺序:
class Foo {
static { bar = generateValue(); } //originally 3rd
private static int generateValue() { return 123; } //originally 2nd
private final static int bar; //originally 1st
public static void main(String[] args) {
System.out.println("Foo.bar = " + Foo.bar);
}
}
事实证明,这编译没有错误。此外,输出为:Foo.bar = 123
。所以,bar
实际上确实包含123
在运行时。但是,以下代码(来自JLS §8.3.1.1j
)会产生编译时错误,因为它在声明之前尝试访问j
:
//Don't do this!
class Z {
static { i = j + 2; } //Produces a compilation error
static int i, j;
static { j = 4; }
}
有趣的是,方法的访问不会以这种方式检查,因此:
class Foo {
static int peek() { return j; }
static int i = peek();
static int j = 1;
public static void main(String[] args) {
System.out.println("Foo.i = " + Foo.i);
}
}
产生以下输出:
Foo.i = 0
发生这种情况是因为
变量初始化器i
使用类方法peek
访问变量的值j
之前j
已被其变量初始化器初始化,此时它仍然具有其默认值——JLS §8.3.2.3
如果相反,在之后i
初始化,则输出为 j
Foo.i = 1
当使用对象而不是原始类型时,情况会变得更糟,例如:
class Foo { //Don't do this
static int peek() { return j.hashCode(); } // NullPointerException here
static int i = peek();
static Object j = new Object();
public static void main(String[] args) {
System.out.println("Foo.i = " + Foo.i);
}
}
这peek
会引发一段NullPointerException
时间的初始化i
:
Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.NullPointerException
at TestGame.Foo.peek(Foo.java:4)
at TestGame.Foo.<clinit>(Foo.java:5)
运行上述代码时,Eclipse 会弹出此窗口:

将其与 OP 联系起来,如果123
该generateValue()
方法不是返回,而是返回某个其他静态字段(或方法)的值,那么 的值bar
取决于代码的文本顺序。
那么,什么时候文本排序很重要?
并不总是使用文本排序。有时 JVM 会提前执行初始化。重要的是要知道何时可以进行前瞻,以及何时以文本顺序进行初始化。JLS在 §8.3.2.3 中描述了初始化期间使用字段的限制(强调我自己的):
仅当成员是 [a] ...类或接口 C 的静态字段并且满足以下所有条件时,成员的声明才需要在使用之前以文本形式出现:
- 用法出现在 [a]... C 的静态变量初始化器或 [a] ...C 的静态初始化器中。
- 用法不在作业的左侧。
- 用法是通过一个简单的名称。
- C 是包含用法的最里面的类或接口。
最后一点:首先初始化常量(根据 JLS §8.3.2.1):
在运行时,最终的静态字段和使用常量表达式(第 15.28 节)初始化的静态字段首先被初始化(第 12.4.2 节)。