概述
弄清楚父子类的加载过程,各个类中静态/非静态变量、静态/非静态代码块、构造方法、普通方法等的加载过程对于推断最终的结果非常重要。
结论
先给出总的结论。
类初始化过程
(1)一个类要创建实例需要先加载并初始化该类。
main()
方法所在的类需要先加载并初始化。
(2)一个子类要初始化需要先初始化父类。
(3)一个类初始化就是执行<clinit>()
方法。
<clinit>()
方法包括:静态变量和静态代码块,他们的加载顺序同执行顺序。且只执行一次。
总的来说,类加载的时候先加载父类,加载的内容是静态代码块和静态变量,加载顺序同代码顺序,只执行一次。
实例初始化过程
实例初始化就是执行<init>()
方法。
①<init>()
方法包括:非静态变量、非静态代码块、对应构造器。
②执行顺序:非静态变量 和 非静态代码块按照代码顺序,构造器是最后执行。
③每次创建实例,调用对应构造器,就是去执行<init>()
方法。
④执行的时候也是先执行父类对应的内容,然后再执行子类。
⑤非静态变量初始化的时候还要考虑重写问题。如果存在重写,是执行子类中的内容。
总的来说,父类先执行,执行内容包括:非静态代码块、非静态变量、构造方法。执行顺序:非静态代码块 和 非静态变量按照代码顺序,构造器最后执行。并且,非静态方法的时候需要考虑重写的问题,重写之后就是执行子类对应重写的方法。 若创建了多个实例,就会执行对应次数的<init>()
方法。
方法重写
另外,关于方法重写的内容,以下这些情况,方法是不能被重写的,包括:
(1)final修饰的方法。
(2)静态方法。
(3)private修饰的方法,导致子类中不可见,所以不可以被重写。
案例
父类:
public class Father {
//非静态变量
private int i = test();
//静态变量
private static int j = method();
//静态代码块
static {
System.out.print("(1)");
}
//构造方法
Father(){
System.out.print("(2)");
}
//非静态代码块
{
System.out.print("(3)");
}
//非静态变量调用
public int test() {
System.out.print("(4)");
return 1;
}
//静态变量方法调用
private static int method() {
System.out.print("(5)");
return 1;
}
}
子类:
public class Son extends Father{
//非静态变量
private int i = test();
//静态变量
private static int j = method();
//静态代码块
static {
System.out.print("(6)");
}
//构造方法
Son(){
System.out.print("(7)");
}
//非静态代码块
{
System.out.print("(8)");
}
//非静态变量调用
public int test() {
System.out.print("(9)");
return 1;
}
//静态变量方法调用
private static int method() {
System.out.print("(10)");
return 1;
}
}
调用:
public static void main(String[] args) {
Son son1 = new Son();
System.out.println();
Son son2 = new Son();
}
分析过程
1.会进行类加载过程。 无论你是否有使用这个对象去创建实例,在类加载的时候就会执行。
类加载的时候,是先加载父类,然后再加载子类。此时执行<clinit>()方法
,加载的是静态相关的内容,静态变量和静态代码块。这二者的加载顺序和代码顺序一致,谁在上面谁就会先加载。
在这里,静态变量在前,静态代码块在后。所以会先加载静态变量j
,在加载的时候会调用method()
方法,即:输出(5)。
然后,加载静态代码块,输出(1)。
此时,父类加载已经完成,就取加载子类中对应的内容,加载顺序同父类,静态变量和静态代码块。
首先,加载静态变量j
,调用method()
方法,输出(10)。
然后,加载静态代码块,输出(6)。
所以,在类加载后的结果就是: (5)(1)(10)(6)。
2.类加载完成之后,才会进入到实际的创建实例的过程。
创建实例的时候,是执行<init>()
方法。包括:非静态变量、非静态代码块、构造方法。构造方法最后,剩下的按照代码顺序。且是先执行父类的,因为有super()
方法。
首先,父类有非静态变量 和 非静态代码块,按照顺序执行,所以先初始化 非静态变量i
,这个时候回调用test()
方法,但是这里需要注意一个问题,这里(父类)的test()
方法被子类重写了,所以最终执行的是子类的test()
方法。所以结果是:输出(9)。
然后,进行非静态代码块,输出(3)。
最后,是构造方法,输出(2)。
上面的父类完了之后,就会到子类进行,顺序也和上面一样。
首先,初始化非静态变量i
,执行自己的test()
方法,输出(9)。
然后,初始化非静态代码块,输出(8)。
最后,子类构造方法,输出(7)。
所以,创建一次Son实例,输出的内容是:(9)(3)(2)(9)(8)(7)。
因为调用的到时候创建了两个实例,所以<init>()
方法被执行两次,所以会再一次输出:(9)(3)(2)(9)(8)(7)。
最终,类加载和实例过程结合起来的输出就是:
(5)(1)(10)(6)(9)(3)(2)(9)(8)(7)
(9)(3)(2)(9)(8)(7)