Java中的类加载顺序

时间:2022-02-23 17:36:39

参考文章:Java类初始化顺序

今天来研究一下Java中类的加载顺序。

一般情况:

Demo1

首先看父类:

public class Father {

static {
System.out.println("这是 Father 的静态代码块");
}

{
System.out.println("这是 Father 的代码块");
}

public Father() {
System.out.println("这是 Father 的构造方法");
}
}

再看子类:

public class Son extends Father{

static {
System.out.println("这是 Son 的静态代码块");
}

{
System.out.println("这是 Son 的代码块");
}

public Son(){
System.out.println("这是 Son 的构造方法");
}
}

然后调用代码:

Son son = new Son();

运行结果如下:

这是 Father 的静态代码块
这是 Son 的静态代码块
这是 Father 的代码块
这是 Father 的构造方法
这是 Son 的代码块
这是 Son 的构造方法

根据以上过程,其实我们可以发现其中的规律了:
1.类加载的时候,如果有父类,会先初始化父类。
2.如果有静态的,就先加载静态的,然后再加载非静态的。

Demo2

把父类和子类稍作修改,分别加了一个静态的成员变量。
父类:

public class Father {

private static Father father = new Father();

static {
System.out.println("这是 Father 的静态代码块");
}

{
System.out.println("这是 Father 的代码块");
}

public Father() {
System.out.println("这是 Father 的构造方法");
}
}

子类:

public class Son extends Father{

private static Son son = new Son();

public Son(){
System.out.println("这是 Son 的构造方法");
}

static {
System.out.println("这是 Son 的静态代码块");
}

{
System.out.println("这是 Son 的代码块");
}
}

同样调用代码:

Son son = new Son();

运行结果如下:

这是 Father 的代码块
这是 Father 的构造方法
这是 Father 的静态代码块
这是 Father 的代码块
这是 Father 的构造方法
这是 Son 的代码块
这是 Son 的构造方法
这是 Son 的静态代码块
这是 Father 的代码块
这是 Father 的构造方法
这是 Son 的代码块
这是 Son 的构造方法

这次结果和上次不太一样,这是因为Father和Son中分别有一个静态成员变量,并且给静态成员变量赋值的时候还调用了各自的构造方法。
还有一点是Father和Son的静态代码块方法,只调用了一次。

我们可以得到如下结论:
1.代码块一定会在构造方法之前加载。
2.静态代码块只会加载一次,同理静态成员变量也是。
如果把上面的两个成员变量都改成非静态的,编译器就会抛出内存溢出异常。因为非静态成员变量是属于对象的,每次创建新的对象时,都会初始化对应的非静态成员变量。然后就出现了构造方法的递归调用)

总结

1.初始化一个类时,如果它有父类,会先去初始化父类。
2.先加载静态的,再加载非静态的。
3.静态的成员变量或者静态的方法块随着类的加载而加载,如果类加载过了,就不用加载了。
4.普通代码块和构造方法都是随着对象的创建而加载,每新创建一个对象,都会加载一次。而且普通代码块在构造方法之前加载。

特殊情况

Demo3

父类:

public class Father {

public static String value = "123";

static {
System.out.println("这是 Father 的静态代码块");
}

{
System.out.println("这是 Father 的代码块");
}

public Father() {
System.out.println("这是 Father 的构造方法");
}
}

子类:

public class Son extends Father{

public Son(){
System.out.println("这是 Son 的构造方法");
}

static {
System.out.println("这是 Son 的静态代码块");
}

{
System.out.println("这是 Son 的代码块");
}
}

调用代码:

    public static void main(String[] args) {
System.out.println(Son.value);
}

运行结果如下:

这是 Father 的静态代码块
123

得出结论:通过子类引用父类的静态字段,不会导致子类初始化

Demo4

父类

public class Father {

static {
System.out.println("这是 Father 的静态代码块");
}

{
System.out.println("这是 Father 的代码块");
}

public Father() {
System.out.println("这是 Father 的构造方法");
}
}

子类:

public class Son extends Father{

public Son(){
System.out.println("这是 Son 的构造方法");
}

static {
System.out.println("这是 Son 的静态代码块");
}

{
System.out.println("这是 Son 的代码块");
}
}

调用代码:

    public static void main(String[] args) {
SuperClass[] sca = new SuperClass[10];
}

运行结果为空。

得出结论:通过数组定义来引用类,不会触发此类的初始化。

Demo5

Son类:

public class Son {

public static final String HELLOWORLD = "hello world";

public Son() {
System.out.println("这是 Son 的构造方法");
}

static {
System.out.println("这是 Son 的静态代码块");
}

{
System.out.println("这是 Son 的代码块");
}
}

调用代码:

    public static void main(String[] args) {
System.out.println(Son.HELLOWORLD);
}

运行结果如下:

    public static void main(String[] args) {
System.out.println(Son.HELLOWORLD);
}

得出结论:常量在编译阶段会存入调用类的常量池中,本质上没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。