Java的初始化块、静态初始化块、构造函数的执行顺序及用途探究
2016-07-24 17:02
393 查看
Java与C++有一个不同之处在于,Java不但有构造函数,还有一个”初始化块“(Initialization Block)的概念。下面探究一下它的执行顺序与可能的用途。
主类Main里面也如法炮制。
C.class的反编译结果
静态初始化块仍然单独分出一部分,输出了我们的调试语句。而另一部分,仍然还是类C的构造函数C();,可以看到它先调用了父类B的构造函数,接着输出了我们初始化块中的语句,然后才输出我们写在构造函数中的语句,最后返回。多次试验也都是如此。于是我们能够推断:初始化块的代码是被加入到子类构造函数的前面,父类初始化的后面了。
静态初始化块
1. 用于初始化静态成员变量
比如给类C增加一个静态成员变量sub,我们在static块里面给它赋值为5:
main函数里输出这个静态变量C.sub:
则输出结果:
符合类被第一次加载时执行静态初始化块的结论,且C.sub被正确赋值为5并输出了出来。
但是乍一看似乎没有什么用,因为静态成员变量在定义时就可以顺便赋值了。因此在赋值方面有点鸡肋。
2. 执行初始化代码
比如可以记录第一次访问类的日志,或方便单例模式的初始化等。对于单例模式,可以先用static块初始化一些可能还被其他类访问的基础参数,等到真正需要加载大量资源的时候(getInstance)再构造单体,在构造函数中加载资源。
非静态初始化块
这个就没什么好说的了,基本跟构造函数一个功能,但比构造函数先执行。最常见的用法应该还是代码复用,即多个重载构造函数都有若干段相同的代码,那么可以把这些重复的代码拉出来放到初始化块中,但仍然要注意它的执行顺序,对顺序有严格要求的初始化代码就不适合使用了。
非静态初始化块和构造函数后执行,并且在每次生成对象时执行一次;
非静态初始化块的代码会在类构造函数之前执行。因此若要使用,应当养成把初始化块写在构造函数之前的习惯,便于调试;
静态初始化块既可以用于初始化静态成员变量,也可以执行初始化代码;
非静态初始化块可以针对多个重载构造函数进行代码复用。
本文基于
知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议发布,欢迎引用、转载或演绎,但是必须保留本文的署名BlackStorm以及本文链接/article/11921747.html,且未经许可不能用于商业目的。如有疑问或授权协商请与我联系。
执行顺序
首先定义A, B, C三个类用作测试,其中B继承了A,C又继承了B,并分别给它们加上静态初始化块、非静态初始化块和构造函数,里面都是一句简单的输出。主类Main里面也如法炮制。
Compiled from "Main.java" class C extends B { C(); Code: 0: aload_0 1: invokespecial #1 // Method B."<init>":()V 4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 7: ldc #3 // String Instance init C. 9: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 12: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 15: ldc #5 // String Constructor C. 17: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 20: return static {}; Code: 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #6 // String Static init C. 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return }
C.class的反编译结果
静态初始化块仍然单独分出一部分,输出了我们的调试语句。而另一部分,仍然还是类C的构造函数C();,可以看到它先调用了父类B的构造函数,接着输出了我们初始化块中的语句,然后才输出我们写在构造函数中的语句,最后返回。多次试验也都是如此。于是我们能够推断:初始化块的代码是被加入到子类构造函数的前面,父类初始化的后面了。
可能的用途:
既然执行顺序和大概原理都摸清了,那么就要探讨一下初始化块的可能的用途。静态初始化块
1. 用于初始化静态成员变量
比如给类C增加一个静态成员变量sub,我们在static块里面给它赋值为5:
class C extends B { static public int a; static { a = 5; System.out.println("Static init C."); } ...... }
main函数里输出这个静态变量C.sub:
public static void main(String[] args) { System.out.println("Value of C.sub: " + C.sub); }
则输出结果:
Static init Main. Static init A. Static init B. Static init C. Value of C.sub: 5
符合类被第一次加载时执行静态初始化块的结论,且C.sub被正确赋值为5并输出了出来。
但是乍一看似乎没有什么用,因为静态成员变量在定义时就可以顺便赋值了。因此在赋值方面有点鸡肋。
2. 执行初始化代码
比如可以记录第一次访问类的日志,或方便单例模式的初始化等。对于单例模式,可以先用static块初始化一些可能还被其他类访问的基础参数,等到真正需要加载大量资源的时候(getInstance)再构造单体,在构造函数中加载资源。
非静态初始化块
这个就没什么好说的了,基本跟构造函数一个功能,但比构造函数先执行。最常见的用法应该还是代码复用,即多个重载构造函数都有若干段相同的代码,那么可以把这些重复的代码拉出来放到初始化块中,但仍然要注意它的执行顺序,对顺序有严格要求的初始化代码就不适合使用了。
总结:
静态初始化块的优先级最高,也就是最先执行,并且仅在类第一次被加载时执行;非静态初始化块和构造函数后执行,并且在每次生成对象时执行一次;
非静态初始化块的代码会在类构造函数之前执行。因此若要使用,应当养成把初始化块写在构造函数之前的习惯,便于调试;
静态初始化块既可以用于初始化静态成员变量,也可以执行初始化代码;
非静态初始化块可以针对多个重载构造函数进行代码复用。
本文基于
知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议发布,欢迎引用、转载或演绎,但是必须保留本文的署名BlackStorm以及本文链接/article/11921747.html,且未经许可不能用于商业目的。如有疑问或授权协商请与我联系。
相关文章推荐
- IO流以日志文件记录异常
- 深入浅出看流媒体前世今生,分分钟二逼变牛逼
- Java异常处理-----运行时异常(RuntimeException)
- Java异常处理-----运行时异常(RuntimeException)
- Java的基础知识1
- JavaSE 高级 第03节 Math类与猜数字游戏
- java构造器的作用
- Java异常处理-----自定义异常
- Java异常处理-----自定义异常
- SpringMVC学习之路
- 转载 《Struts2返回JSON数据的具体应用范例》
- Java CountDownLatch 和 CyclicBarrier 使用
- Java异常处理-----抛出处理
- Java异常处理-----抛出处理
- 使用eclipse自带插件生产WebService客户端代码
- 运行Eclipse出错:Failed to load the JNI shared library
- Java8 Spliterator 接口 原理
- RandomAccessFile
- 一、javaSE总结
- 线程基础二--卖票问题