摘自:http://my.oschina.net/ostatsu/blog/526092
引言
在开发 Java 应用程序时,很多场景下需要使用到静态初始化块(Static Initialization Blocks)。本文将对其进行简要分析。
介绍
静态初始化块是一个封闭在标记了 static 关键字的花括号中的普通代码块。静态初始化块在类被初始化时执行,且在类的整个生命周期中只执行一次。所以通常用于对类的静态域进行初始化工作,以及执行某些需要确保在类被构造时优先执行的代码。
应用
在 JDK 源码中,有几个类都使用了以下代码:
1
2
3
4
5
|
private
static
native
void
registerNatives();
static
{
registerNatives();
}
|
这里使用了静态初始化块,以确保在类被构造时马上调用 registerNatives 方法来注册一些本地函数。
静态初始化块还可以用在一个常量工具类中,用来从配置文件中初始化静态属性:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
public
class
Constants {
private
static
final
String PFILE =
"constants"
;
private
static
String property =
""
;
private
Constants() {
}
static
{
copyProperties(PFILE);
}
private
static
void
copyProperties(String fileName) {
ResourceBundle rb = ResourceBundle.getBundle(fileName);
Map<String, Object> map =
new
HashMap<String, Object>();
Constants constants =
new
Constants();
Enumeration<String> enums = rb.getKeys();
while
(enums.hasMoreElements()) {
String key = enums.nextElement();
map.put(key, rb.getString(key).trim());
}
try
{
BeanUtils.copyProperties(constants, map);
}
catch
(IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
public
static
String getProperty() {
return
property;
}
public
void
setProperty(String property) {
Constants.property = property;
}
}
|
执行时机
静态初始化块在类被初始化时执行,具体与其他代码块之间的执行顺序是什么?
示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public
class
Demo {
static
{
System.out.println(
"Static initialization block."
);
}
{
System.out.println(
"Initialization block."
);
}
public
Demo() {
System.out.println(
"Constructor."
);
}
public
static
void
staticMethod() {
System.out.println(
"Static method."
);
}
public
void
dynamicMethod() {
System.out.println(
"Dynamic method."
);
}
}
|
1
2
3
4
5
6
7
8
9
|
public
class
Main {
public
static
void
main(String args[])
throws
ClassNotFoundException {
System.out.println(
"Main method."
);
Class.forName(
"Demo"
);
Class.forName(
"Demo"
);
}
}
|
执行结果:
1
2
|
Main method.
Static initialization block.
|
当使用 Class.forName 方法加载类时,类被初始化,静态初始化块被执行,且只执行了一次。
与非静态方法
修改 main 方法:
1
2
3
4
|
public
static
void
main(String args[]) {
System.out.println(
"Main method."
);
new
Demo().dynamicMethod();
}
|
执行结果:
1
2
3
4
5
|
Main method.
Static initialization block.
Initialization block.
Constructor.
Dynamic method.
|
执行顺序为:静态初始化块 -> 初始化块 -> 构造方法 -> 实例方法。
与静态方法
修改 main 方法:
1
2
3
4
|
public
static
void
main(String args[]) {
System.out.println(
"Main method."
);
Demo.staticMethod();
}
|
执行结果:
1
2
3
|
Main method.
Static initialization block.
Static method.
|
静态初始化块也会优先于静态方法执行。
与 main 方法
如果把上述 main 方法拷到 Demo 类中执行呢?
执行结果:
1
2
3
|
Static initialization block.
Main method.
Static method.
|
静态初始化块同样会优先于静态方法执行,同时也会优先于 main 方法执行。
这是因为在类中定义了 main 方法做为程序的入口的情况下,属于类被直接引用,当然会触发类的初始化。
Core Java 中就曾提到,可以编写一个没有 main 方法的 Hello, World 程序:
1
2
3
4
5
|
public
class
Hello {
static
{
System.out.println(
"Hello, World!"
);
}
}
|
执行 java Hello 查看结果:
1
2
|
Hello, World!
Exception in thread
"main"
java.lang.NoSuchMethodError: main
|
可以添加 System.exit(0) 避免输出异常信息。
但本程序在 JDK 1.7 下会直接报错:
1
2
|
错误: 在类 Hello 中找不到主方法, 请将主方法定义为:
public
static
void
main(String[] args)
|
与静态域
静态初始化块与静态变量之间的执行顺序是什么?
示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public
class
Demo {
static
{
x =
200
;
}
public
static
int
x =
100
;
public
static
void
main(String args[]) {
System.out.println(
"x = "
+ Demo.x);
}
}
|
执行结果:
1
|
x =
100
|
静态变量 x 的值并没有得到改变,那么静态初始化块之内的代码有没有执行?
在静态初始化块内赋值语句前后输出 x 的值:
1
2
3
4
5
|
static
{
System.out.println(
"x1 = "
+ Demo.x);
x =
2
;
System.out.println(
"x2 = "
+ Demo.x);
}
|
执行结果:
1
2
3
|
x1 =
0
x2 =
200
x =
100
|
说明以上代码在静态初始化块中的赋值语句先于变量定义中的赋值语句执行,那么为什么却能访问到该静态变量?
去掉打印语句后查看反汇编代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
Compiled from
"Demo.java"
public
class
Demo {
public
static
int
x;
static
{};
Code:
0
: sipush
200
3
: putstatic #
10
// Field x:I
6
: bipush
100
8
: putstatic #
10
// Field x:I
11
:
return
public
Demo();
Code:
0
: aload_0
1
: invokespecial #
15
// Method java/lang/Object."<init>":()V
4
:
return
public
static
void
main(java.lang.String[])
throws
java.lang.ClassNotFoundException;
Code:
0
:
return
}
|
可以看到,静态变量的定义语句被分为了两部分,先进行定义,之后进行了赋值。而静态变量定义时的赋值操作被放在了静态初始化块的赋值操作之后。
实际上,在使用 javac 编译生成字节码时,编译器会把类构造器 <clinit> 方法添加到语法树中,类初始化时, JVM 会按顺序将静态域和静态初始化块的逻辑全部封装到 <clinit> 方法内。
修改代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public
class
Demo {
public
static
int
x =
100
;
static
{
x =
200
;
}
public
static
void
main(String args[]) {
System.out.println(
"x = "
+ Demo.x);
}
}
|
执行结果:
1
|
x =
200
|
所以为了避免混淆,在给静态域赋值时最好使用静态方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public
class
Demo {
public
static
int
x = initX();
public
static
int
initX() {
return
200
;
}
public
static
void
main(String args[]) {
System.out.println(
"x = "
+ Demo.x);
}
}
|
上面提到,静态域和静态初始化块是按照顺序执行的,那么以下代码的执行结果会是什么呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public
class
Demo {
public
static
Demo demo =
new
Demo();
static
{
System.out.println(
"Static initialization block."
);
}
{
System.out.println(
"Initialization block."
);
}
public
Demo() {
System.out.println(
"Constructor."
);
}
public
static
void
main(String args[]) {
System.out.println(
"Main method."
);
}
}
|
执行结果:
1
2
3
4
|
Initialization block.
Constructor.
Static initialization block.
Main method.
|
这种特殊的情况下,静态初始化块会晚于构造方法执行。
继承时
示例代码:
1
2
3
4
5
6
7
8
9
10
11
|
public
class
SuperClass {
static
{
System.out.println(
"Super static initialization block."
);
}
public
SuperClass() {
System.out.println(
"Super constructor."
);
}
}
|
1
2
3
4
5
6
7
8
9
10
11
|
public
class
SubClass
extends
SuperClass {
static
{
System.out.println(
"Sub static initialization block."
);
}
public
SubClass() {
System.out.println(
"Sub constructor."
);
}
}
|
1
2
3
4
5
6
7
|
public
class
Main {
public
static
void
main(String[] args) {
new
SubClass();
}
}
|
执行结果:
1
2
3
4
|
Super
static
initialization block.
Sub
static
initialization block.
Super constructor.
Sub constructor.
|
本例中,实例化一个子类对象,类被加载,会进行类的初始化。运行时系统在初始化子类时,要先初始化父类,所以会先执行父类的静态初始化块再执行子类的静态初始化块,然后实例化子类对象时需要先实例化父类对象,所以通常顺序为 父类初始化块 -> 子类初始化块 -> 父类构造方法 -> 子类构造方法。如果遇到更复杂的情况需要具体分析。
总结
简单总结,静态初始化块会在类被初始化时执行一次,通常情况下优先于该类的所有其他方法执行,与静态域之间的执行顺序同源码编写顺序。执行子类静态初始化块之前会先执行父类静态初始化块。其他复杂情况具体问题具体分析。