java静态初始化块学习

摘自: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.

本例中,实例化一个子类对象,类被加载,会进行类的初始化。运行时系统在初始化子类时,要先初始化父类,所以会先执行父类的静态初始化块再执行子类的静态初始化块,然后实例化子类对象时需要先实例化父类对象,所以通常顺序为 父类初始化块 -> 子类初始化块 -> 父类构造方法 -> 子类构造方法。如果遇到更复杂的情况需要具体分析。

总结

简单总结,静态初始化块会在类被初始化时执行一次,通常情况下优先于该类的所有其他方法执行,与静态域之间的执行顺序同源码编写顺序。执行子类静态初始化块之前会先执行父类静态初始化块。其他复杂情况具体问题具体分析。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值