首先把结论贴出来:
1. 类的加载顺序。
(1) 父类静态代码块(包括静态初始化块,静态属性,但不包括静态方法)
(2) 子类静态代码块(包括静态初始化块,静态属性,但不包括静态方法 )
(3) 父类非静态代码块( 包括非静态初始化块,非静态属性 )
(4) 父类构造函数
(5) 子类非静态代码块 ( 包括非静态初始化块,非静态属性 )
(6) 子类构造函数
Point1:关于静态代码块(包括静态属性的声明和静态的代码块),是严格按照在类中的出现顺序执行的。如果你在前面声明了一个静态属性 static int mm = 1; 那么你可以在这句声明位置之后的静态代码块中引用该属性,如 static { mm += 1}; ,但是绝对不能在声明该静态属性之前引用该属性,否则编译不会通过,而是会报前向引用的错误。
Point2:对于类的非静态代码块(非静态代码块和非静态属性的声明),我们可以简单地将它们全部按照顺序写在该类的所有的构造器方法(不管有参还是无参)的开头,在执行顺序上与P1一样,也是严格按照出现顺序执行的。
Point3:根据以上两个结论,对于一个要分析加载顺序的类,可以把它改写成只有两个部分的类,即静态部分和非静态部分。静态部分是将所有的static属性以及static代码块全部按照顺序写在一起,非静态部分则是把非static的属性和非static的代码块全部按照顺序写在所有的构造器方法的最开头。 这样就可以把类的加载顺序合成为:
- 父类静态代码块部分
- 子类静态代码块部分
- 父类构造器
- 子类构造器
2. 我们知道,如果调用子类的无参构造器,则会在子类的构造器方法的开头默认调用父类的构造器方法super();那么,如果调用子类的含参构造器方法,是否会调用父类的有参构造器方法或是无参构造器?
答案是: 不管调用的子类构造器方法是有参还是无参的,只要没有在构造器方法的开头显式地写出super(a,b,c)这样调用有参父类构造器方法,就会默认地调用父类的无参构造器。
进而,类的加载顺序:
- 父类静态部分
- 子类静态部分
- 父类无参构造器(除非子类显式地调用了父类的有参构造器)
- 子类构造器(这个是在调用的时候由我们指定的)
--------------
实践方法:构建三个类,Parent、Son和ClassLoadingSeqTest类,来测试。
Parent.java
public class Parent {
final static int CONSTANT_PARENT = 666;
static int ParentNum = 10;
static int Construct_Signal = 11;
private int notFinal = 2;
static {
int m1 = 6;
System.out.println("m1:" + m1);
}
public Parent() {
System.out.println("Parent无参构造器方法被调用");
Construct_Signal = 16;
System.out.println("Construct_Signal:" + Construct_Signal);
System.out.println("即将从Parent构造器中调用printFinal和NotFinal");
printFinal();
this.printFinal(); // 等同于printFinal()
printNotFinal();
}
public Parent(int notFinal){
System.out.println("Parent的有参构造器方法");
this.notFinal = notFinal;
}
public void printFinal() {
System.out.println("Parent_Final:" + CONSTANT_PARENT);
}
public void printNotFinal() {
System.out.println("Parent_NotFinal:" + notFinal);
}
static {
String m2 = "abcd";
System.out.println("Parent_m2:" + m2);
}
{
System.out.println("Parent 非静态代码块");
}
}
Son.java
public class Son extends Parent {
final static int CONSTANT_PARENT = 555;
static int ParentNum = 10;
static int Construct_Signal = 11;
private int notFinal = 1;
public Son() {
System.out.println("Son构造器方法被调用");
Construct_Signal = 15;
System.out.println("Construct_Signal:" + Construct_Signal);
printFinal();
printNotFinal();
}
public Son(int notFinal) {
//super(notFinal);
System.out.println("Son的有参构造器方法被调用");
this.notFinal = notFinal;
}
@Override
public void printFinal() {
System.out.println("Son_Final:" + CONSTANT_PARENT);
}
@Override
public void printNotFinal() {
System.out.println("Son_NotFinal:" + notFinal);
}
static {
String m3 = "bcde";
System.out.println("Son_m3:" + m3);
}
{
System.out.println("Son 非静态代码块");
}
}
ClassLoadingSeqTest.java
public class ClassLoadingSeqTest {
public static void main(String[] args) {
System.out.println("----------Son无参构造过程----------");
Parent p1 = new Son();
System.out.println("----------Son有参构造过程----------");
Parent p2 = new Son(5);
}
}
运行结果:
----------Son无参构造过程----------
m1:6
Parent_m2:abcd
Son_m3:bcde
Parent 非静态代码块
Parent无参构造器方法被调用
Construct_Signal:16
即将从Parent构造器中调用printFinal和NotFinal
Son_Final:555
Son_Final:555
Son_NotFinal:0
Son 非静态代码块
Son无参构造器方法被调用
Construct_Signal:15
Son_Final:555
Son_NotFinal:1
----------Son有参构造过程----------
Parent 非静态代码块
Parent无参构造器方法被调用
Construct_Signal:16
即将从Parent构造器中调用printFinal和NotFinal
Son_Final:555
Son_Final:555
Son_NotFinal:0
Son 非静态代码块
Son的有参构造器方法被调用
先根据运行结果分析子类有参/无参构造器的问题: 可以看到不管我们是通过有参的还是无参的子类构造器来创建一个子类对象,在子类的构造器方法的开头都是默认调用了父类的无参构造器!除非在子类构造器方法中显式地调用super(notFinal)方法,才能调用对应的父类的有参构造器。
下面考虑加载顺序的问题。我们使用IDE可以对编译出的文件进行反编译,获得class文件的代码(IDEA是可以的,打开工程目录下的out文件夹中的对应class文件就可以查看代码。Eclipse应该也可以)。
现在我们打开Son.class:
Son.class
public class Son extends Parent {
static final int CONSTANT_PARENT = 555;
static int ParentNum = 10;
static int Construct_Signal = 11;
private int notFinal = 1;
public Son() {
System.out.println("Son 非静态代码块");
System.out.println("Son无参构造器方法被调用");
Construct_Signal = 15;
System.out.println("Construct_Signal:" + Construct_Signal);
this.printFinal();
this.printNotFinal();
}
public Son(int notFinal) {
System.out.println("Son 非静态代码块");
System.out.println("Son的有参构造器方法被调用");
this.notFinal = notFinal;
}
public void printFinal() {
System.out.println("Son_Final:555");
}
public void printNotFinal() {
System.out.println("Son_NotFinal:" + this.notFinal);
}
static {
String m3 = "bcde";
System.out.println("Son_m3:" + m3);
}
}
可以看到class文件与java文件很重要的一个区别:非静态代码块不见了,而是被写在了每一个有参或无参的构造器方法的开头。这就可以说明非静态代码块的执行顺序是紧挨着构造器方法的。
本文详细探讨了Java中类的加载顺序,包括静态和非静态代码块的执行顺序以及构造器的调用规则。总结了类的加载分为静态部分和非静态部分,静态部分按照顺序执行父类和子类的静态代码块,非静态部分则在构造器中先调用父类构造器再执行子类构造器。无论子类构造器是否有参数,若未显式调用父类有参构造器,都会默认调用父类无参构造器。
839





