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("子类 -- 非静态代码块(构造代码块)"); } }
我的理解:
类的实例化其实就是遵循:静态代码块 -----> 非静态代码块(构造代码块) --------> 构造函数。
但是因为继承关系,子类继承了父类的一些成员变量、静态变量、方法等等,在子类中是可以直接使用的,因此如果需要实例化子类的对象时,要按照顺序,先把父类加载,然后加载子类,
再实例化父类对象,最后才实例化子类对象。