Java与C++有一个不同之处在于,Java不但有构造函数,还有一个”初始化块“(Initialization Block)的概念。下面探究一下它的执行顺序与可能的用途。

执行顺序

  首先定义A, B, C三个类用作测试,其中B继承了A,C又继承了B,并分别给它们加上静态初始化块、非静态初始化块和构造函数,里面都是一句简单的输出。

  主类Main里面也如法炮制。

 class A {
static {
System.out.println("Static init A.");
} {
System.out.println("Instance init A.");
} A() {
System.out.println("Constructor A.");
}
} class B extends A {
static {
System.out.println("Static init B.");
} {
System.out.println("Instance init B.");
} B() {
System.out.println("Constructor B.");
}
} class C extends B { static {
System.out.println("Static init C.");
} {
System.out.println("Instance init C.");
} C() {
System.out.println("Constructor C.");
}
} public class Main { static {
System.out.println("Static init Main.");
} {
System.out.println("Instance init Main.");
} public Main() {
System.out.println("Constructor Main.");
} public static void main(String[] args) {
C c = new C();
//B b = new B();
}
}

测试代码

  当然这里不使用内部类,因为内部类不能使用静态的定义;而用静态内部类就失去了一般性。

  那么可以看到,当程序进入了main函数,并创建了一个类C的对象之后,输出是这样子的:

Static init Main.
Static init A.
Static init B.
Static init C.
Instance init A.
Constructor A.
Instance init B.
Constructor B.
Instance init C.
Constructor C.

  观察上面的输出,可以观察到两个有趣的现象:

  1. Main类是肯定没有被实例化过的,但是由于执行main入口函数用到了Main类,于是static初始化块也被执行了;
  2. 所有的静态初始化块都优先执行,其次才是非静态的初始化块和构造函数,它们的执行顺序是:
    1. 父类的静态初始化块
    2. 子类的静态初始化块
    3. 父类的初始化块
    4. 父类的构造函数
    5. 子类的初始化块
    6. 子类的构造函数

  那么如果有多个实例化对象,又会不会发生变化呢?于是在第一个C类的对象后面,再实例化一个B类的对象,再观察输出:

Static init Main.
Static init A.
Static init B.
Static init C.
Instance init A.
Constructor A.
Instance init B.
Constructor B.
Instance init C.
Constructor C.
Instance init A.
Constructor A.
Instance init B.
Constructor B.

  可以发现这输出跟前面的基本长得一样对吧?只是在后面多了4行,那是新的B类对象实例化时产生的信息,同样也是父类A的初始化块和构造函数先执行,再轮到子类B的初始化块和构造函数执行;同时还发现,静态初始化块的输出只出现了一次,也就是说每个类的静态初始化块都只在第一次实例化该类对象时执行一次。

  无论如何,初始化块和构造函数总在一起执行是件有趣的事情,让我们反编译一下看看吧!

  查看生成目录发现已经生成了4个.class文件,分别是A.class, B.class, C.class, Main.class,先看看Main.class的结构(这里重新注释了new B):

 javap -c Main
 Compiled from "Main.java"
public class Main {
public Main();
Code:
: aload_0
: invokespecial # // Method java/lang/Object."<init>":()V
: getstatic # // Field java/lang/System.out:Ljava/io/PrintStream;
: ldc # // String Instance init Main.
: invokevirtual # // Method java/io/PrintStream.println:(Ljava/lang/String;)V
: getstatic # // Field java/lang/System.out:Ljava/io/PrintStream;
: ldc # // String Constructor Main.
: invokevirtual # // Method java/io/PrintStream.println:(Ljava/lang/String;)V
: return public static void main(java.lang.String[]);
Code:
: new # // class C
: dup
: invokespecial # // Method C."<init>":()V
: astore_1
: return static {};
Code:
: getstatic # // Field java/lang/System.out:Ljava/io/PrintStream;
: ldc # // String Static init Main.
: invokevirtual # // Method java/io/PrintStream.println:(Ljava/lang/String;)V
: return
}

Main.class的反编译结果

  可以看到整个Main类被分成三个部分,static {}部分很显然,就是我们的static初始化块,在里面调用了println并输出了String“Static init Main.”;而main入口函数也很清晰,首先新实例化了一个类C的对象,然后调用了类C的构造函数,最后返回;而上面public Main();的部分就很有意思了,这是类Main的构造函数,但我们看到里面调用了两次println,分别输出了String“Instance init Main.”和String“Constructor Main.”。难道初始化块和构造函数被合并到一起了?

  我们再看看C类的反编译结果吧:

 javap -c C
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)再构造单体,在构造函数中加载资源。

 非静态初始化块

  这个就没什么好说的了,基本跟构造函数一个功能,但比构造函数先执行。最常见的用法应该还是代码复用,即多个重载构造函数都有若干段相同的代码,那么可以把这些重复的代码拉出来放到初始化块中,但仍然要注意它的执行顺序,对顺序有严格要求的初始化代码就不适合使用了。

总结:

  1. 静态初始化块的优先级最高,也就是最先执行,并且仅在类第一次被加载时执行;
  2. 非静态初始化块和构造函数后执行,并且在每次生成对象时执行一次;
  3. 非静态初始化块的代码会在类构造函数之前执行。因此若要使用,应当养成把初始化块写在构造函数之前的习惯,便于调试;
  4. 静态初始化块既可以用于初始化静态成员变量,也可以执行初始化代码
  5. 非静态初始化块可以针对多个重载构造函数进行代码复用

本文基于知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议发布,欢迎引用、转载或演绎,但是必须保留本文的署名BlackStorm以及本文链接http://www.cnblogs.com/BlackStorm/p/5699965.html,且未经许可不能用于商业目的。如有疑问或授权协商请与我联系

Java的初始化块、静态初始化块、构造函数的执行顺序及用途探究的更多相关文章

  1. Java基础 静态块、非静态块、构造函数的执行顺序

    Java中经常有一些静态块,这是用来在生成类之前进行的初始化,无论java还C++语言中的static,都是最先初始化好的.结构如下: static { 静态语句代码块 } { 非静态语句代码块 }  ...

  2. 关于java中构造方法、实例初始化、静态初始化执行顺序

    在Java笔试中,构造方法.实例初始化.静态初始化执行顺序,是一个经常被考察的知识点. 像下面的这道题(刚刚刷题做到,虽然做对了,但是还是想整理一下) 运行下面的代码,输出的结果是... class ...

  3. C# 父子类_实例_静态成员变量_构造函数的执行顺序

    今天去面试的时候被一道题问得一点脾气都没有,今天特地来研究下. 子类成员变量,子类静态成员变量,子类构造函数,父类成员变量,父类静态成员变量,父类构造函数的执行顺序. 现在贴上从另外一个.net程序员 ...

  4. java基础课程笔记 static 主函数 静态工具类 classpath java文档注释 静态代码块 对象初始化过程 设计模式 继承 子父类中的函数 继承中的构造函数 对象转型 多态 封装 抽象类 final 接口 包 jar包

    Static那些事儿 Static关键字 被static修饰的变量成为静态变量(类变量) 作用:是一个修饰符,用于修饰成员(成员变量,成员方法) 1.被static修饰后的成员变量只有一份 2.当成员 ...

  5. JavaEE初始化时静态代码块加载问题

    1.使用java.exe命令运行某个类的时java.exe Person2.创建一个类的对象时Person p=new Person();3.访问类中的静态成员变量(赋值/获取值)System.out ...

  6. 非静态代码块(非static初始化块)&静态代码块(static初始化块)

    非静态代码块: TestOrder: package com.aff.singleton; /* 类的第四个成员:初始化块(代码块) 代码块: 如果有修饰的话只能使用static 分类:非静态代码块: ...

  7. Java子父类间静态代码块、非静态代码块、构造方法的执行顺序

    子类A继承父类B,A a=new A(); 正确的执行顺序是:父类B静态代码块->子类A静态代码块->父类B非静态代码块->父类B构造函数->子类A非静态代码块->子类A ...

  8. java基础-Map的静态初始化以及Map的遍历等.....................

    1.map的静态初始化,以及map遍历的几种方法: package com.cy.test; import java.util.HashMap; import java.util.Iterator; ...

  9. java中父类子类静态代码块、构造代码块执行顺序

    父类静态(代码块,变量赋值二者按顺序执行) 子类静态 父类构造代码块 父类构造方法 子类构造代码块 子类构造方法 普通方法在实列调用的时候执行,肯定位于上面之后了 //父类A public class ...

随机推荐

  1. T-sql语句查询执行顺序

    前言 数据库的查询执行,毋庸置疑是程序员必备技能之一,然而数据库查询执行的过程绚烂多彩,却是很少被人了解,今天哥哥要带你装逼带你飞,深入一下这sql查询的来龙去脉,为查询的性能优化处理打个基础,或许面 ...

  2. 【.net 深呼吸】记录WCF的通信消息

    前面老周给大伙伴们介绍了把跟踪信息写入日志文件的方法,今天咱们换个类似的话题来扯一下,对了,咱们就说说怎么把WCF的往来消息log下来吧. 尽管在现实生活中,我们不主张偷窥他人信息,不过,偷窥程序信息 ...

  3. PHP 面向对象编程和设计模式 (5/5) - PHP 命名空间的使用及名称解析规则

    PHP高级程序设计 学习笔记 2014.06.12 命名空间概述 PHP 在 5.3.0 以后的版本开始支持命名空间.什么是命名空间?从广义上来说,命名空间是一种封装事物的方法.在很多地方都可以见到这 ...

  4. h5engine造轮子

    基于学习的造轮子,这是一个最简单,最基础的一个canvas渲染引擎,通过这个引擎架构,可以很快的学习canvas渲染模式! 地址:https://github.com/RichLiu1023/h5en ...

  5. SQL 性能调优中可参考的几类Lock Wait

    在我们的系统出现性能问题时,往往避不开调查各种类型 Lock Wait,如Row Lock Wait.Page Lock Wait.Page IO Latch Wait等.从中找出可能的异常等待,为性 ...

  6. Storm介绍及与Spark Streaming对比

    Storm介绍 Storm是由Twitter开源的分布式.高容错的实时处理系统,它的出现令持续不断的流计算变得容易,弥补了Hadoop批处理所不能满足的实时要求.Storm常用于在实时分析.在线机器学 ...

  7. Vertica数据库常用管理命令汇总

    1.查询数据库是否有等待 select * from resource_queues where node_name=(select node_name from nodes order by nod ...

  8. redis命令1

    SADD numbers 1 3 5 创建一个名为numbers的intset SADD fruites "apple" "peach" 创建一个hashtab ...

  9. Event Sourcing Pattern 事件源模式

    Use an append-only store to record the full series of events that describe actions taken on data in ...

  10. 一位同事对 Rafy 框架的一些建议及我的回复

    下面是一位同事对当前的产品开发框架提出的一些建议,以及我的回复.我觉得一些问题提得有一定的代表性,在征得本人同意后,将本邮件发布在博客中. 同时,也非常希望对框架.产品有好的建议的小伙伴,都可以给我发 ...