先看代码案例
package com.exemple.demo.test.preciate;
public class Father {
private int i = test();
private static int j = method();
public Father() {
System.out.print("(3)");
}
static {
System.out.print("(1)");
}
{
System.out.print("(2)");
}
private static int method() {
System.out.print("(4)");
return 0;
}
public int test() {
System.out.print("(5)");
return 0;
}
}
class Son extends Father{
private int i = test();
private int k = test();
private static int j = method();
static {
System.out.print("(6)");
}
{
System.out.print("(7)");
}
public Son() {
System.out.print("(8)");
}
public Son(int i) {
this.i = i;
}
public Son(int i, int k) {
this(i);
this.k = k;
}
private static int method() {
System.out.print("(9)");
return 0;
}
public int test() {
System.out.print("(10)");
return 0;
}
public static void main(String[] args) {
Son son = new Son();
System.out.println();
Son son2 = new Son();
}
}
执行结果
(4)(1)(9)(6)(5)(2)(3)(10)(7)(8)
(5)(2)(3)(10)(7)(8)
静态上下文的执行时机
类加载场景
常见的主动加载情况
- 实例化一个对象时
- 通过反射加载一个类
- 调用一个类的静态方法/属性
- 读写一个类的静态属性
- 实例化子类对象时(会触发父类的加载)
类加载过程
- 加载阶段:类加载器ClassLoader从网络或本地文件系统中加载字节码文件到JVM中
- 链接阶段:将验证Class文件中的字节流包含的信息是否符合当前虚拟机的要求,为静态域分配存储空间并设置类变量的初始值(默认的零值)
- 初始化:到了此阶段,才真正开始执行类中定义的java程序代码(字节码层面即执行clinit)。clinit即按在代码中出现的顺序执行该类的静态变量初始化和类的静态代码块,如果该类有父类的话,则优先对其父类进行初始化
字节码clinit案例
以Son类的cinit方法为例
构造器
为了初始化成员属性,而不是初始化对象,初始化对象是通过new关键字实现的,new关键字会在堆上为对象开辟一块空间用来存储对象自己的属性、方法,如果有父类的话也会存储父类相关的非private的属性和方法,因为创建子类对象会调用父类构造方法但不会创建父类对象,只是调用父类构造方法初始化父类成员属性并一块存入到子类对象中
特殊案例
抽象类可以有构造方法,但抽象类不能实例化
字节码init方法
java中的构造器代码会编译为init方法,init方法中的内容首先是执行父类的init方法(子类构造器第一行其实就是super,只不过父类没有多参构造器、或者在有多参构的情况下显示定义了无参构造,那么子类构造器第一行的super()可以省略),随后按代码顺序初始化类属性、普通代码块,(始终)最后执行构造方法,构造方法的执行始终是最后一步去执行的,和代码顺序无关,这意味着构造器赋值拥有最终解释权,实例对象属性最后的值是通过构造器赋予的,一个构造器对应一个init方法,一个类有n个构造器,就会对应n个init方法。
init方法执行过程注意点
init方法按代码顺序初始化类属性的过程中,可能会受到子类重写方法的影响,因为我们在main方法中实例化的是Son类的对象,在实例化Son之前初始化父类Father时,加载父类属性i的时需要执行test方法,看test方法是否被子类给重写了,如果发生了重写,那么此时默认执行的是子类方法中的test,代码执行结果为
(4)(1)(9)(6)(10)(2)(3)(10)(7)(8)
(10)(2)(3)(10)(7)(8)
作为对比,由于我们上述案例中父类中的test方法修饰符是private,所以子类并未发生重写,所以此时执行的还是父类的test,代码执行结果为
(4)(1)(9)(6)(5)(2)(3)(10)(7)(8)
(5)(2)(3)(10)(7)(8)
通过this调用构造器
init方法案例
以Son类的构造器init方法为例