正如@Mureinik 所说,“总之——是的。这个代码是完全合法的。”我想提供一个更彻底的答案,因为在将静态初始化程序与类方法结合使用时很容易出现问题——出现的顺序会影响类状态,这是一个讨厌的错误 .
类中声明的静态初始化器在类初始化时执行。与类变量的任何字段初始化器一起...静态初始化器可用于初始化类的类变量 -- Java Language Specification (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.1)会产生编译时错误,因为它会在声明 j 之前尝试访问 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在初始化i时抛出一个NullPointerException:
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 联系起来,如果 generateValue() 方法不是返回 123,而是返回某个其他静态字段(或方法)的值,那么 bar 的值取决于代码。
那么,什么时候文本排序很重要?
并不总是使用文本排序。有时 JVM 会提前执行初始化。重要的是要知道何时可以进行前瞻,以及何时以文本顺序进行初始化。 JLS 描述了Restrictions on the use of Fields during Initialization in §8.3.2.3(强调我自己的):
只有当成员是 [a] ...类或接口 C 的静态字段并且 所有 满足以下条件时,成员的声明才需要在使用之前以文本形式出现:
- 在 [a]... C 的静态变量初始化器或 [a] ...C 的静态初始化器中使用。
- 用法不在作业的左侧。
- 用法是通过一个简单的名称。
- C 是包含用法的最内层类或接口。
最后一点:常量首先被初始化(根据 JLS §8.3.2.1):
在运行时,最终的静态字段和使用常量表达式(第 15.28 节)初始化的静态字段首先被初始化(第 12.4.2 节)。