在类中同时拥有初始化块,静态代码块时,代码运行的顺序是怎样的?创建对象时是先调用构造器么?下面根据写的demo来探究Java创建对象时代码的运行顺序.

一、案例演示

package top.clearlight.question;

public class SubClass extends SuperClass {
static {
System.out.println("子类 静态代码块");
}

public static int staticIntSub = printInt("子类 静态成员变量");

{
System.out.println("子类 初始代码块");
}

SubClass() {
super(10);
System.out.println("The End");
}

SubClass(int a) {
this();
System.out.println(a);
}

public static void main(String[] args) {
SuperClass superClass = new SuperClass();
System.out.println("------------------");
SubClass subClass = new SubClass(10);
}
}

class SuperClass {
int a = printInt("父类 成员变量初始化 (前)");
{
System.out.println("父类 初始代码块 (构造方法前)");
}

static {
System.out.println("父类 静态代码块 (前)");
}

SuperClass() {
System.out.println("父类 无参构造函数");
}

SuperClass(int a) {
System.out.println("父类 ---- 有参构造函数");
}

int k = printInt("父类 成员变量初始化 (后)");

{
System.out.println("父类 初始代码块 (构造方法后)");
}

static int printInt(String s) {
System.out.println(s);
return 1;
}

public static int staticInt = printInt("父类 静态成员变量");

static {
System.out.println("父类 静态代码块 (后)");
}
}

对于上面代码的结果,先自己写写看,与运行之后是否有差异,然后再看后面的结果与结论.不明白继续看分析.

运行结果 :

初始化块、静态代码块、构造函数的运行顺序 - Java_静态代码块

反编译后代码 :



二、执行顺序结论

对于拥有父类的子类创建对象时,代码的执行顺序

首先是在创建对象之前,先执行父类的静态代码块或静态成员初始化(按照代码顺序执行),然后执行子类的静态代码块或静态成员初始化(按照代码顺序执行)(静态代码块与静态成员只执行一次).

接下来开始创建对象

  • 若是创建父类对象,那么先执行代码块或成员变量的初始化(按照代码顺序执行),最后执行父类对应的构造函数.
  • 若是创建子类对象,那么看子类是否在相应的构造函数调用super(参数),调用的父类那个构造器,然后后按照上面的顺序执行一遍,之后开始执行子类代码块或成员变量的初始化(按照代码顺序执行),最后执行子类对应的构造函数里面的内容.
  • 若是创建指向子类的父类的对象,执行顺序与创建子类对象的顺序相同.当然如果调用方法,则按照多态的规则进行调用.

三、案例分析

难点1 静态代码块或静态成员变量按顺序执行?

对于执行顺序,通过反编译工具运行一遍编译后的文件其实就一目了然了.这里展示一下编译后的代码是怎样的.
对于父类的静态代码块和静态成员变量的初始化,反编译后 :

public static int staticInt;
static {
System.out.println("父类 静态代码块 (前)");
staticInt = printInt("父类 静态成员变量");
System.out.println("父类 静态代码块 (后)");
}

会按照顺序全部归结到静态代码块中,并且在创建对象之前,先执行静态代码块里面的内容且只执行一次


难点2 代码块以及类的成员变量的执行顺序?

对于代码块以及类成员变量的初始化,都会归结到每个构造函数中.

SuperClass()
{
a = printInt("父类 成员变量初始化 (前)");
System.out.println("父类 初始代码块 (构造方法前)");
k = printInt("父类 成员变量初始化 (后)");
System.out.println("父类 初始代码块 (构造方法后)");
System.out.println("父类 无参构造函数");
}

SuperClass(int a) i
{
this.a = printInt("父类 成员变量初始化 (前)");
System.out.println("父类 初始代码块 (构造方法前)");
k = printInt("父类 成员变量初始化 (后)");
System.out.println("父类 初始代码块 (构造方法后)");
System.out.println("父类 ---- 有参构造函数");
}

如果一个构造函数通过​​this(参数)​​调用了本类中另一个构造函数,那么该构造函数将不会归结代码块以及成员变量初始化的内容,因为调用的构造函数已经归结了,所以共用调用的构造函数归结的成员变量以及初始化的内容.

SubClass()
{
super(10);
System.out.println("子类 初始代码块");
System.out.println("The End");
}

SubClass(int a)
{
this();
System.out.println(a);
}

难点3 super(参数)的如何执行?

类中如果不写构造函数,那么该类会有默认的构造函数.
如果是子类的话,如果不显示调用父类构造器的话(通过​​​super(参数)​​来调用对应的父类构造器),那么使用的子类构造函数会默认调用父类无参的构造函数.

因此,创建子类对象时,会先执行子类调用的对应的父类构造函数,然后在执行子类构造函数中的内容(编译后已经将代码块和成员变量的初始化按照顺序添加到该构造函数super(参数)之后和构造函数中内容之前了)


四、小案例

明白上面的案例之后,看下面这个应该不难,在检验一下自己吧.

package top.clearlight.question;

public class EmptyCodeBlock {
private static int uid = 10;
private int id = 1;

// 初始化块
{
System.out.println(id);
System.out.println("空代码块,在构造方法之前");
}

public EmptyCodeBlock() {
id = 2;
System.out.println("无参构造函数");
}

public EmptyCodeBlock(int uid) {
this.uid = uid;
}

// 静态代码块
static {
System.out.println("静态代码块前");
}

{
System.out.println(id);
System.out.println("空代码块,在构造方法之后");
}

static {
System.out.println("静态代码块后");
}

public static void main(String[] args) {
EmptyCodeBlock ecb = new EmptyCodeBlock();
System.out.println(ecb.id);

System.out.println(EmptyCodeBlock.uid);
EmptyCodeBlock ecb2 = new EmptyCodeBlock(20);
System.out.println(ecb2.id);
System.out.println(EmptyCodeBlock.uid);
}
}


运行结果

初始化块、静态代码块、构造函数的运行顺序 - Java_父类_02


五、总结

在创建对象时 :

静态成员变量初始化 与 静态代码块(按照代码顺序)(先执行父类在执行子类) -> this(参数)或super(参数)对应构造函数的内容 -->类中成员变量的初始化 与 初始化块(按照代码顺序) -> 构造函数的内容

  • 对于​​静态成员变量以及静态代码块​​,都是​static​关键字修饰的,在类第一次加载的时候,将会执行静态域的初始化.且只执行一次
  • ​初始化块​​ : 只要构造类的对象,这些块就会被先执行.

第一次写完这篇博客的时候,测试的太少了,当看到这篇​​Java 类初始化(详解)​​的测试用例,试验之后才知道自己刚开始是错的.然后作了修改,不知是否还有错误,记录一下,欢迎大家订正,感谢先.
- -2019.8.21



如果感觉本文对您有帮助,希望可以点赞支持一下作者.当然我也会继续写一些有用的东西,可以关注我持续看后面内容.感谢支持.

细微动作,如蔷薇般芬芳