Java类的加载顺序
父类静态代变量、
父类静态代码块、
子类静态变量、
子类静态代码块、
父类非静态变量(父类实例成员变量)、
父类构造函数、
子类非静态变量(子类实例成员变量)、
子类构造函数。
上面的说法也能解决大部分问题,但是还是没有理解到java初始化的核心,二话不说先来一道题
/**
* @author ZFX
* @date2019/5/14 16:51.
*/
public class Animal {
private int i = test();
private static int j = method();
static {
System.out.println("a");
}
Animal(){
System.out.println("b");
}
{
System.out.println("c");
}
public int test(){
System.out.println("d");
return 1;
}
public static int method(){
System.out.println("e");
return 1;
}
}
/**
* @author ZFX
* @date2019/5/14 16:54.
*/
public class Dog extends Animal{
private int i = test();
private static int j = method();
static {
System.out.println("f");
}
Dog(){
System.out.println("g");
}
{
System.out.println("h");
}
public int test(){
System.out.println("i");
return 1;
}
public static int method(){
System.out.println("j");
return 1;
}
public static void main(String[] args) {
Dog dog = new Dog();
System.out.println();
Dog dog1 = new Dog();
}
}
说出上面代码的执行结果就行
本题覆盖的知识点很多,我们慢慢来分析
类的初始化时机
在《深入理解java虚拟机》这本书里面讲到了类的六个初始化时机
- 创建类的实例
- 访问类的静态变量(注意:当访问类的静态并且final修饰的变量时,不会触发类的初始化。),或者为静态变量赋值。
- 调用类的静态方法(注意:调用静态且final的成员方法时,会触发类的初始化!一定要和静态且final修饰的变量区分开!!)
- 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。如:Class.forName("********");注意通过类名.class得到Class文件对象并不会触发类的加载。
- 初始化某个类的子类
- 直接使用java.exe命令来运行某个主类(java.exe运行,本质上就是调用main方法,所以必须要有main方法才行)。
代码精析
- 类的初始化过程
从dog的代码开始分析当执行到main方法时,我们发现他符合类的初始化时机的第六条,此时触发类的初始化,这本身有符合父类Animal的第五条,所以会触发Animal类的初始化,一个类的初始化就是执行clinit()方法
- ()方法有静态类变量显示赋值代码和静态代码块组成
- 类变量的显示赋值代码和静态代码块代码从上到下的顺序执行
- ()方法只执行一次
上一步的执行结果为 e a j f - 实例的初始化
实例的初始化就是执行init() 方法
init()方法可能重载很多个,有几个构造器就有几个init方法
init()方法由非静态实例变量显示赋值代码和非静态代码块、对应构造器代码组成
非静态实例变量显示赋值代码和非静态代码块代码从上到下的顺序执行,而对应的构造器最后执行
每次创建实例对象,调用对应的构造器,执行的就是对应的init方法
init()方法的首行代码是super()会调用父类的init()方法 - 方法的重写test的方法在子类中被重写了,所以初始化时会执行子类重写的代码,这是面向对象多态性的体现
为什么method不用考虑重写,应为静态方法是类方法,不会被子类重写
不能被重写的方法
- final方法
- 静态方法
- private等子类不可见的方法
最终结果