Java初始化执行顺序

前言

关于Java类的成员初始化顺序,一直是笔试中常见的基础考题,今天就执行顺序写一篇博文

主要从:构造函数,构造代码块,静态代码块,继承关系分析

构造函数

public A(){
    //构造函数  to do something 
 }
  1. 对象新建时,调用对应的构造函数
  2. 构造函数的作用是用于给对象进行初始化
  3. 一个对象建立,构造函数只运行一次,而一般方法可以被该对象调用多次

构造代码块

{
    //构造代码块 to do something    
}
public class InitTest {

    static int i = 2;

    {
        i *= 2;
    }

    public InitTest(){
        System.out.println("构造函数:" + i);
    }

    public static void main(String[] args) {
        System.out.println(i);
        InitTest test = new InitTest();
        System.out.println(i);
    }

    {
        i *= 3;
    }
}
运行结果:
2
构造函数:12
12
  1. 构造代码块是在对象创建的时候执行的,如果没有创建对象不会执行构造代码块,而且构造代码块执行顺序先于构造函数的执行顺序
  2. 构造代码块的作用是给对象进行初始化
  3. 构造代码块与构造函数的区别是:构造代码块是给所有对象进行统一初始化,而构造函数是给对应的对象初始化,构造函数可以多次调用,而构造代码块第一次创建对象就会调用,而且仅一次

静态代码块

public class InitTest {

    static int i = 2;

    static{
        i *= 2;
    }


    public static void main(String[] args) {
        System.out.println(i);

    }

    static{
        i *= 3;
    }
}
结果:
12
  1. 静态代码块随着类的加载而执行,只执行一次,并且优先于主函数。具体说,静态代码块是由类调用的。类调用时,先执行静态代码块,然后才执行主函数的。
  2. 静态代码块其实就是给类初始化的,而构造代码块是给对象初始化的。

    单个类中例子

public class InitTest {

    InitTest() {
        System.out.println("构造函数");
    }

    {
        System.out.println("构造代码块");
    }

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

    public static void main(String[] args) {
        InitTest test1 = new InitTest();
        InitTest test2 = new InitTest();
    }
}
结果:
静态代码块
构造代码块
构造函数
构造代码块
构造函数

结论:执行顺序从先到后为: 静态变量->静态代码块->变量->构造代码块-> 构造函数

继承中的初始化顺序

父类:

package com.hq.initTest;

/**
 * 父类
 * @author Huangqing
 * @date 2018/6/27 15:54
 */
public class A {

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

    {
        System.out.println("父类: 构造代码块");
    }

    public A(){
        System.out.println("父类:构造函数");
    }

    private static String staticParentString = staticInitParent();

    private  String parentString = initParent();

    public static String staticInitParent(){
        System.out.println("父类:静态方法,被静态成员变量赋值调用");
        return "staticInitParent";
    }

    public String initParent() {
        System.out.println("父类:普通成员方法,被普通成员变量赋值调用");
        return "initParent";
    }
}

子类:

package com.hq.initTest;

/**
 * 子类
 * @author Huangqing
 * @date 2018/6/27 15:54
 */
public class B extends A {

    private static String staticChildString = staticInitChild();

    private  String childString = initChild();

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

    {
        System.out.println("子类: 构造代码块");
    }

    public B(){
        System.out.println("子类:构造函数");
    }


    public static String staticInitChild(){
        System.out.println("子类:静态方法,被静态成员变量赋值调用");
        return "staticInitParent";
    }

    public String initChild() {
        System.out.println("子类:普通成员方法,被普通成员变量赋值调用");
        return "initParent";
    }
}

主方法:

package com.hq.initTest;

/**
 * 测试Java类的成员和初始化块(代码块)初始化顺序
 * @author Huangqing
 * @date 2018/6/27 15:53
 */
public class InitTest {

    public static void main(String[] args) {
        System.out.println("主函数执行开始");
        new B();
        System.out.println("主函数执行结束");
    }
}
运行结果:
主函数执行开始
父类:静态代码块
父类:静态方法,被静态成员变量赋值调用
子类:静态方法,被静态成员变量赋值调用
子类:静态代码块
父类: 构造代码块
父类:普通成员方法,被普通成员变量赋值调用
父类:构造函数
子类:普通成员方法,被普通成员变量赋值调用
子类: 构造代码块
子类:构造函数
主函数执行结束

经测试,得到在继承关系中,父类和子类成员初始化执行顺序结论:

1.父类【静态成员】和【静态代码块】,按在代码中出现的顺序依次执行。
2.子类【静态成员】和【静态代码块】,按在代码中出现的顺序依次执行。
3.父类的【普通成员变量被普通成员方法赋值】和【普通代码块】,按在代码中出现的顺序依次执行。
4.执行父类的构造方法。
5.子类的【普通成员变量被普通成员方法赋值】和【普通代码块】,按在代码中出现的顺序依次执行。
6.执行子类的构造方法。

附上一道阿里的Java初始化执行顺序题

public class TestConstructOrder {

    public static int k = 0;
    public static int k1;
    public int k2;
    public static TestConstructOrder t1 = new TestConstructOrder("t1");
    public static TestConstructOrder t2 = new TestConstructOrder("t2");
    public static int i = print("i");
    public static int n = 99;
    public int j = print("j");
    {
        print("构造块");
    }
    static {
        print("静态块");
    }

    public TestConstructOrder(String str) {
        System.out.println((++k) + ":" + str + "    i=" + i + "    n=" + n);
        ++i;
        ++n;
    }

    public static int print(String str) {
        System.out.println((++k) + ":" + str + "    i=" + i + "    n=" + n);
        ++n;
        return ++i;
    }

    /**
     * 
     */
    public static void main(String[] args) {
        TestConstructOrder t = new TestConstructOrder("init");
    }

}

结果是:
1:j i=0 n=0
2:构造块   i=1 n=1
3:t1    i=2 n=2
4:j i=3 n=3
5:构造块   i=4 n=4
6:t2    i=5 n=5
7:i i=6 n=6
8:静态块   i=7 n=99
9:j i=8 n=100
10:构造块  i=9 n=101
11:init i=10    n=102

整个的执行步骤:(下面的分析不包括父类内容)
加载类信息:
1、静态变量默认初始化
2、对静态变量赋值,加载静态代码块中内容 (赋值顺序按书写顺序)
创建对象:
1、实例变量初始化
2、实例变量赋值(依然按顺序)
3、构造代码块中内容
4、构造函数中内容

分析:
1、 首先Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象。

2、加载类信息,对静态变量默认初始化,此时并未赋值(初始化顺序与代码顺序无关,具体可能要研究JVM虚拟机)
public static int k // 初始化, k = 0
public static int k1; // 初始化,k1 = 0
public static TestConstructOrder t1 = null // 初始化类 t1, t1 = null
public static TestConstructOrder t2 = null // 初始化类 t2, t2 = null
public static int i // 初始化 i = 0
public static int n = 99; // 初始化 n = 0

3、对静态变量赋值,加载静态代码块中内容。
k = 0 // k赋值0
t1 = new TestConstructOrder (“t1”) // 这里创建一个对象

注意 这里因为要创建对象,所以会执行创建对象的步骤:

实例变量初始化:
public int k2;// 初始化k2 , k2 = 0
public int j; // 初始化j, j = 0

实例变量赋值:
j = print(“j”) // 会执行静态方法

打印结果:1:j i = 0 n = 0

因为在创建对象,而我们前面说过,在创建对象时,构造代码块先于构造函数执行,所以会执行构造代码块

打印结果:2:构造块 i = 1 n = 1

然后执行构造函数

打印结果:3:t1 i = 2 n = 2

同样的,给t2赋值也会跟t1一样

打印结果:
4:j i = 3 n = 3
5:构造快 i = 4 n = 4
6:t2 i = 5 n = 5

继续赋值:i = print(“i”)
打印结果:
7:i i = 6 n = 6

对n赋值:int n = 99 // 此时 n = 99

然后执行静态代码块

打印结果:
8:静态块 i = 7 n = 99

最后:执行主函数,新建对象,这里的初始化顺序不再多说了,跟t1的相同

打印结果:
9:j i = 8 n = 100
10:构造快 i = 9 n = 101
6:init i = 10 n = 102

相信这道题,已经足够让你了解Java类成员的初始化顺序,博主也是一开始被弄糊涂了,如果还有不明白的地方,欢迎留言。如果觉得写得不错,别忘了给点个赞~

你可能感兴趣的:(java)