java静态代码块、构造代码块、构造方法的执行顺序

一、静态代码块

静态代码块:执行优先级高于非静态的初始化块,在类加载时执行,并且只执行一次,执行完成便销毁,它仅能初始化类变量,即static修饰的数据成员。
静态代码块写法:

static {
  // 代码
}

示例1:

public class StaticBlock {
    int i = 100;
    static float f = 3.14f;

    static {
        // 报错:java: 无法从静态上下文中引用非静态 变量 i
        // System.out.println(i);

        // 静态代码块可以引用静态变量
        System.out.println(f);
        f = 6.66f;
        System.out.println(f);
    }

    public static void main(String[] args) {

    }
}

 

示例2:

public class StaticBlock {
    static {
        // 报错:java: 非法前向引用
        System.out.println(f);
    }

    static float f = 3.14f;

    public static void main(String[] args) {

    }
}

报错原因分析:因为静态代码块在类加载时执行,而静态变量也是在类加载时初始化,它们是在同一时间发生,所以有顺序要求,如果在静态代码块中要访问静态变量,那么静态变量必须放到静态代码块之前。

 

二、构造代码块

构造代码块(非静态代码块、动态代码块):执行的时候如果有静态初始化块,先执行静态初始化块再执行非静态初始化块,在每个对象生成时都会被执行一次,它可以初始化类的实例变量。非静态代码块会在构造函数执行时,在构造函数主体代码执行之前被运行
构造代码块写法:

{
//
}

示例1:

public class StaticBlock {
    int i = 1;

    {
        // 构造代码块中可以引用成员变量
        System.out.println(i);
    }

    public static void main(String[] args) {
        // 实例化对象时,先执行构造代码块
        StaticBlock obj = new StaticBlock();
    }
}

 

示例2:

public class StaticBlock {
    {
        // 报错:java: 非法前向引用
        System.out.println(i);
    }

    int i = 1;

    public static void main(String[] args) {
        StaticBlock obj = new StaticBlock();
    }
}

报错原因分析:因为构造代码块在对象实例化时执行,而成员变量也是在对象实例化时初始化,它们是在同一时间发生,所以有顺序要求,如果在构造代码块中要访问成员变量,那么成员变量必须放到构造代码块之前。

 

示例3

public class StaticBlock {
    {
        // 能够引用静态变量
        System.out.println(s);
    }

    static int s = 1;

    public static void main(String[] args) {
        StaticBlock obj = new StaticBlock();
    }
}

分析:因为静态变量在类加载时初始化,而构造代码块是在对象实例化时执行的,因此无论顺序,构造代码块中都可以访问静态变量。

 

 

三、静态代码块、构造代码块、构造方法的执行顺序

1、单个类中的执行顺序

单个类中的执行顺序:静态代码块 -----> 非静态代码块(构造代码块) --------> 构造函数

示例:

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

    StaticBlock() {
        System.out.println("构造方法");
    }

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

    public static void main(String[] args) {
        StaticBlock obj = new StaticBlock();
    }
}

结果:

静态代码块
非静态代码块(构造代码块)
构造方法

 

2、类继承的执行顺序

1、在子类实例化子类对象

1.类先进行加载:父类加载(此时会执行父类静态代码块) --> 子类加载(此时会执行子类静态代码块)

2.执行子类的main方法,遇到实例化子类对象

3.先实例化父类对象,因为子类继承了父类,也会用到父类的对象方法,因此父类对象先实例化(此时会执行构造代码块和构造方法),然后实例化子类对象(此时会执行构造代码块和构造方法)

 

2、在父类实例化子类对象

1.类先进行加载:父类加载(此时会执行父类静态代码块)

2.执行父类的main方法,遇到实例化子类对象,此时去加载子类(此时会执行子类静态代码块)

3.先实例化父类对象,因为子类继承了父类,也会用到父类的对象方法,因此父类对象先实例化(此时会执行构造代码块和构造方法),然后实例化子类对象(此时会执行构造代码块和构造方法)

 

示例1:在子类实例化子类对象

/**
 * 父类:文件名 -- Father
 */
public class Father {
    static {
        System.out.println("父类 -- 静态代码块");
    }

    Father() {
        System.out.println("父类 -- 构造方法");
    }

    {
        System.out.println("父类 -- 非静态代码块(构造代码块)");
    }

}

// ---------------------------- 分割线 -------------------------------------

/**
 * 子类:文件名 -- Son
 */
public class Son extends Father {
    static {
        System.out.println("子类 -- 静态代码块");
    }

    Son() {
        System.out.println("子类 -- 构造方法");
    }

    {
        System.out.println("子类 -- 非静态代码块(构造代码块)");
    }

    public static void main(String[] args) {
        System.out.println("子类 -- main方法");
        Son obj = new Son();
    }
}

/*
 * 结果:
 * 父类 -- 静态代码块
 * 子类 -- 静态代码块
 * 子类 -- main方法
 * 父类 -- 非静态代码块(构造代码块)
 * 父类 -- 构造方法
 * 子类 -- 非静态代码块(构造代码块)
 * 子类 -- 构造方法
 *
 * 在子类实例化子类对象时
 * 1.先加载父类,执行父类的静态代码块
 * 2.然后加载子类,执行子类的静态代码块,然后进入main方法
 * 3.实例化父类对象,执行父类构造代码块和构造方法
 * 4.实例化子类对象,执行子类构造代码块和构造方法
 */

 

示例2:在父类实例化子类对象

/**
 * 父类:文件名 -- Father
 */
public class Father {
    static {
        System.out.println("父类 -- 静态代码块");
    }

    Father() {
        System.out.println("父类 -- 构造方法");
    }

    {
        System.out.println("父类 -- 非静态代码块(构造代码块)");
    }

    public static void main(String[] args) {
        System.out.println("父类 -- main方法");
        Son obj = new Son();
    }
}

/*
 * 结果:
 * 父类 -- 静态代码块
 * 父类 -- main方法
 * 子类 -- 静态代码块
 * 父类 -- 非静态代码块(构造代码块)
 * 父类 -- 构造方法
 * 子类 -- 非静态代码块(构造代码块)
 * 子类 -- 构造方法
 *
 * 在父类实例化子类对象时
 * 1.先加载父类,执行父类的静态代码块,然后进入main方法
 * 2.然后加载子类,执行子类的静态代码块
 * 3.实例化父类对象,执行父类构造代码块和构造方法
 * 4.实例化子类对象,执行子类构造代码块和构造方法
 */

// ------------------------------------ 分割线 --------------------------------------

/**
 * 子类:文件名 -- Son
 */
public class Son extends Father {
    static {
        System.out.println("子类 -- 静态代码块");
    }

    Son() {
        System.out.println("子类 -- 构造方法");
    }

    {
        System.out.println("子类 -- 非静态代码块(构造代码块)");
    }

}

我的理解:

类的实例化其实就是遵循:静态代码块 -----> 非静态代码块(构造代码块) --------> 构造函数

但是因为继承关系,子类继承了父类的一些成员变量、静态变量、方法等等,在子类中是可以直接使用的,因此如果需要实例化子类的对象时,要按照顺序,先把父类加载,然后加载子类,

再实例化父类对象,最后才实例化子类对象。

 

posted @ 2022-09-13 22:59  我用python写Bug  阅读(359)  评论(0编辑  收藏  举报