java static的初始化注意事项
类的load/link/initialization是Java中最为基础的知识,细节的信息在JVM规范中有描述,说下关于static的初始化(有些时候还是挺容易写错)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public class A { public static void main(String[] args) { System.out.println(B.NICK); System.out.println(B.NAME); } } public class B{ public static final String NICK = "nick"; public static final String NAME = getName(); private static String getName(){ return "bluedavy"; } static{ System.out.println("hellojava"); } } |
上面这段代码执行的输出为:
nick
hellojava
bluedavy
不按规范里那么学术化的来解释上面的原因,感兴趣的可以对上面编译后的A、B两个类用javap -c -verbose -private来查看里面的具体信息,javap -c -verbose B后可以看到如下的关键信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public static final java.lang.String NICK; Constant value: String nick public static final java.lang.String NAME; static {}; Code: Stack=2, Locals=0, Args_size=0 0: invokestatic #14; //Method getName:()Ljava/lang/String; 3: putstatic #18; //Field NAME:Ljava/lang/String; 6: getstatic #20; //Field java/lang/System.out:Ljava/io/PrintStream; 9: ldc #26; //String hellojava 11: invokevirtual #28; //Method java/io/PrintStream.println:(Ljava/lang/St ring;)V 14: return |
可以看到NICK后面标为Constant value,而NAME的赋值则放在了static block中,java在将源码编译为字节码时,会将static对应的赋值动作或源码中的多个static block(例如{…} static{…})按照顺序全部放到static block中,如用btrace在运行时要跟踪这个static block的执行,可跟踪方法,就类似构造器在运行时对应的方法名为。
在编译为字节码后,对于B的代码而言,其实相当于变成了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public class B{ public static final String NICK = "nick"; public static final String NAME; private static String getName(){ return "bluedavy"; } static{ NAME = getName(); System.out.println("hellojava"); } } |
至于为什么在调用B.NICK的时候没触发static block的执行,javap看下A的字节码就懂了,查看时可看到如下关键信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 | public static void main(java.lang.String[]); Code: Stack=2, Locals=1, Args_size=1 0: getstatic #16; //Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #22; //String nick 5: invokevirtual #24; //Method java/io/PrintStream.println:(Ljava/lang/St ring;)V 8: getstatic #16; //Field java/lang/System.out:Ljava/io/PrintStream; 11: getstatic #30; //Field com/bluedavy/spike/B.NAME:Ljava/lang/String ; 14: invokevirtual #24; //Method java/io/PrintStream.println:(Ljava/lang/St ring;)V 17: return |
可看到System.out.println(B.NICK)这行相当于被直接改为了System.out.println(“nick”),仔细看过JVM规范的同学会知道对于constant类型的static final变量,在编译为字节码阶段就会直接被替换为对应的值,这里有个小小的坑是可能会碰到的,就是编译的时候只编译了对应的static final变量的代码,但没重编译相应引用的代码,那就悲催了,运行的时候还会是之前的值。
本文固定链接: http://www.chepoo.com/java-static-initialization-considerations.html | IT技术精华网