最完整类加载顺序总结,只需要这一篇就足够了

作者 | Java圣斗士,转载请注明出处

全文阅读大约需要10分钟

我:强子,又去面试了吗?感觉怎么样?

强子:嗨,别提了,面试遇到了一个类加载顺序的问题,要求写出输出结果,估计是跪了。

我:啊?怎么会呢?这个问题你不应该是手到擒来吗?

强子:是啊,最近准备面试,面试题也看了不少,也的确复习过类加载顺序的问题,可是这回它又变了个样,结果就蒙圈了!

我:哈哈,那你倒是说说,面试题是怎么样的呢?

强子:下面就是面试题的内容:

class A {
 private static int numA;
 private int numA2;
 
 static {
 System.out.println("A的静态字段 : " + numA);
 System.out.println("A的静态代码块");
 }
 
 {
 System.out.println("A的成员变量 : " + numA2);
 System.out.println("A的非静态代码块");
 }
 
 public A() {
 System.out.println("A的构造器");
 }
 
 public A(int n) {
 System.out.println("A的有参构造");
 this.numA2 = n;
 }
}
 
class B extends A {
 private static int numB;
 private int numB2;
 
 static {
 System.out.println("B的静态字段 : " + numB);
 System.out.println("B的静态代码块");
 }
 
 {
 System.out.println("B的成员变量 : " + numB2);
 System.out.println("B的非静态代码块");
 }
 
 public B() {
 System.out.println("B的构造器");
 }
 
 public B(int n) {
 System.out.println("B的有参构造");
 this.numB2 = n;
 }
}
 
public class ClassLoad {
 public static void main(String[] args) {
 B b = new B(1);// 思考有参构造的输出结果
 }
}

我:我的天,这道题还真长,我看你光读题就已经没耐心了吧?那你是怎么分析的呢?

强子:先来简单说说这道题。上面的这段程序定义了一个父类A 和一个子类 B ,父类中有:静态变量、成员变量(非静态变量)、静态代码块、非静态代码块、无参构造、有参构造。子类 B也是同样的内容。在 main 函数中,创建一个对象 b 调用了 B 的有参构造,问输出结果是什么。

如果是按照常理来出题的话,一般都在main中直接调用 B类(子类) 的无参构造,然后观察输出结果。这回可倒好,居然调用的是B 的有参构造,晕。

我:恩,是的呢!这种题型的思路,一定要紧紧抓住加载顺序静态资源要优先于非静态资源,父类的构造器要优先于子类的构造器。但是这道题很明显增加了很多更加不按套路出牌的知识,比如非静态代码块,甚至是静态字段和成员变量的加载顺序都要求你写出来。的确是超级变态。

在Java 虚拟机规范中,有相关的描述,不过都是些非常晦涩难懂的术语,其实总结起来就是下面这一条:

父类的静态字段——>父类静态代码块——>子类静态字段——>子类静态代码块——>

父类成员变量(非静态字段)——>父类非静态代码块——>父类构造器——>子类成员变量——>子类非静态代码块——>子类构造器

所以,上面这道题的答案已经可以说出个八九不离十了。应该是:

A的静态字段 : 0
A的静态代码块
B的静态字段 : 0
B的静态代码块
A的成员变量 : 0
A的非静态代码块
A的构造器
B的成员变量 : 0
B的非静态代码块
B的有参构造

强子:原来是这样啊,我也执行过这段面试题的代码,的确如你所说分好不差。

但是我纠结的是,这道题的 main 函数中调用的是 B 的有参构造,在调用子类构造器之前,我们都知道会先调用父类的构造器,但是却并没有记得有说是父类的有参构造器还是无参构造器,那为什么最终调用的是父类的无参构造器呢?那如果我只给A定义了一个有参构造器,而没有定义A的无参构造器,那又会出现什么问题呢?

我:恩,这也是这道题特别别扭的地方。

我们平时的面试题都是直接调用子类的无参构造,但是这道题却调用了有参构造。如果A,也就是父类只有有参构造而没有无参构造的话,那么将会直接编译错误,你的IDE就会出现醒目的红色警告!

这是因为,编译器在编译到程序调用了子类构造器的时候,不论是否有参无参,都会隐式地调用父类的无参构造,因为是隐式的,所以无法给父类的构造器指定参数,因此必然执行的是父类的无参构造。这回你明白了吧?

强子:原来是这样啊,调用子类构造器的时候,不论是否有参,都会隐式调用父类的无参构造。之前还真的是忽视了这个挺不起眼的点。不过这道面试题能够兼顾这么多类加载顺序中能够出现的程序元素还真是不容易,静态字段、成员变量、静态代码块、非静态代码块、有参构造、无参构造,真是应有尽有,这道面试题如果是弄懂了,那以后这种类型的面试题就都没问题了。

我:恩,这也算是因祸得福吧,期待你下次会发挥出更好的水平。

总结

类加载顺序的面试题在笔试时经常出现,几乎逢考必中,认真对待这类问题,不仅要有非常良好的JVM知识,同时需要有极强的归纳总结能力。

完整的加载顺序是:

父类的静态字段——>父类静态代码块——>子类静态字段——>子类静态代码块——>

父类成员变量(非静态字段)——>父类非静态代码块——>父类构造器——>子类成员变量——>子类非静态代码块——>子类构造器

总体的原则就是先静态,后动态,先父类,后子类,同时,要注意,当子类调用的是有参构造的时候,父类依然会调用无参构造来创建对象,如果父类没有声明无参构造,编译将会报错

好了,以上就是关于一道类加载顺序问题的总结,希望大家喜欢我的讲解风格,多多关注是对我最大的鼓励。

---欢迎关注【Java圣斗士】,我是你们的小可爱(✪ω✪) Morty---

---专注IT职场经验、IT技术分享的灵魂写手---

---每天带你领略IT的魅力---

---期待与您陪伴!---

举报
评论 0