一次性搞清 Java 中的类加载问题
摘要:很多时候提到类加载,大家总是没法马上回忆起顺序,这篇文章会用一个例子为你把类加载的诸多问题一次性澄清。
本文分享自华为云社区《用1个例子加5个问题,一次性搞清java中的类加载问题【奔跑吧!JAVA】》,原文作者:breakDraw 。
很多时候提到类加载,大家总是没法马上回忆起顺序,这篇文章会用一个例子为你把类加载的诸多问题一次性澄清。
Java 类的加载顺序引用 1 个网上的经典例子,并做稍许改动,以便大家更好地理解。
原例子引用自:https://blog.csdn.net/zfx2013/article/details/89453482
执行这段 main 程序,会输出什么?答案是 eafjicbhigicbhig
为了方便大家一个个细节去理解, 我换一种方式去提问。
Q: 什么时候会进行静态变量的赋值和静态代码块的执行?
A:
第一次创建某个类或者某个类的子类的实例
访问类的静态变量、调用类的静态方法
使用反射方法 forName
调用主类的 main 方法(本例子的第一次静态初始化其实属于这个情况,调用了 Dog 的 main 方法)
注: 类初始化只会进行一次, 上面任何一种情况触发后,之后都不会再引起类初始化操作。
Q:初始化某个子类时,也会对父类做静态初始化吗?顺序呢?
A:如果父类之前没有被静态初始化过,那就会进行, 且顺序是先父类再子类。 后面的非静态成员初始化也是如此。所以会先输出 eafj。
Q: 为什么父类的 method 不会被子类的 method 重写?
A: 静态方法是类方法,不会被子类重写。毕竟类方法调用时,是必定带上类名的。
Q: 为什么第一个输出的是 e 而不是 a?
A: 因为类变量的显示赋值代码和静态代码块代码按照从上到下的顺序执行。Animal 的静态初始化过程中,method 的调用在 static 代码块之前,所以先输出 e 再输出 a。而 Dog 的静态初始化过程中,method 的调用在 static 代码块之后,因此先输出 f,再输出 j
Q: 没有在子类的构造器中调用 super()时,也会进行父类对象的实例化吗?
A: 会的。会自动调用父类的默认构造器。 super()主要是用于需要调用父类的特殊构造器的情况。因此会先进行 Animal 的对象实例化,再进行 Dog 的对象实例化
Q: 构造方法、成员显示赋值、非静态代码块(即输出 c 和 h 的那 2 句)的顺序是什么?
A:
1、成员显示赋值、非静态代码块(按定义顺序)
2、构造方法
因此 Animal 的实例化过程输出 icb(如果对输出 i 有疑问,见下面一题)接着进行 Dog 的实例化,输出 hig
Q: 为什么 Animal 实例化时, i=test()中输出的是 i 而不是 d?
A:因为你真正创建的是 Dog 子类,Dog 子类中的 test()方法由于签名和父类 test 方法一致,因此 test 方法被重写了。此时即使在父类中调用,也还是用使用子类 Dog 的方法。除非你 new 的是 Animal。
Q: 同上题, 如果 test 方法都是 private 或者 final 属性, 那么上题的情况会有变化吗??
A:因为 private 和 final 方法是不能被子类重写的。所以 Animal 实例化时,i=test 输出 d。
总结一下顺序:
父类静态变量显式赋值、父类静态代码块(按定义顺序)
子类静态变量显式赋值、子类静态代码块(按定义顺序)
父类非静态变量显式赋值(父类实例成员变量)、父类非静态代码块(按定义顺序)
父类构造函数子类非静态变量(子类实例成员变量)、子类非静态代码块(按定义顺序)
子类构造函数。
类加载过程
Q:类加载的 3 个必经阶段是:
A:
加载(类加载器读取二进制字节流,生成 java 类对象)
链接(验证,分配静态域初始零值)
初始化(前面的题目讲的其实就是初始化时的顺序)
更详细的如下:
被动引用中和类静态初始化的关系
Q:new 某个类的数组时,会引发类初始化吗?像下面输出什么
A:new 数组时,不会引发类初始化。什么都不输出。
Q:引用类的 final 静态字段,会引发类初始化吗?像下面输出什么?
A: 不会引发。不会输出 initA。 去掉 final 就会引发了。(注意这里必须是基本类型常量, 如果是引用类型产量,则会引发类初始化)
Q:子类引用了父类的静态成员,此时子类会做类初始化嘛?如下会输出什么
A:子类不会初始化。打印 initA,却不会打印 initB。
类加载器
双亲委派
类加载时的双亲委派模型,不知道能怎么出题。。。反正就记得优先去父类加载器中看类是否能加载。
就贴个图吧:
注意,上面的图有问题。Bootsrap 不是 ClassLoader 的子类,他是 C++编写的。而 ExtClassLoader 和 AppClassLoader 都是继承自 ClassLoader 的
Q:java 中, 是否类和接口的包名和名字相同, 那么就一定是同一个类或者接口?
A:错误。1 个 jvm 中, 类和接口的唯一性由 二进制名称以及它的定义类加载器 共同决定。
因此 2 个不同的加载器加载出来相同的类或接口时, 实际上是不同的。
版权声明: 本文为 InfoQ 作者【华为云开发者社区】的原创文章。
原文链接:【http://xie.infoq.cn/article/69cd110273068db615725b865】。文章转载请联系作者。
评论