摘要:不少时候提到类加载,你们老是无法立刻回忆起顺序,这篇文章会用一个例子为你把类加载的诸多问题一次性澄清。
本文分享自华为云社区《用1个例子加5个问题,一次性搞清java中的类加载问题【奔跑吧!JAVA】》,原文做者:breakDraw 。java
不少时候提到类加载,你们老是无法立刻回忆起顺序,这篇文章会用一个例子为你把类加载的诸多问题一次性澄清。数组
Java类的加载顺序
引用1个网上的经典例子,并作稍许改动,以便你们更好地理解。jvm
原例子引用自:https://blog.csdn.net/zfx2013/article/details/89453482函数
public class Animal { private int i = test(); private static int j = method(); static { System.out.println("a"); } Animal(){ System.out.println("b"); } { System.out.println("c"); } public int test(){ System.out.println("d"); return 1; } public static int method(){ System.out.println("e"); return 1; } } public class Dog extends Animal{ { System.out.println("h"); } private int i = test(); static { System.out.println("f"); } private static int j = method(); Dog(){ System.out.println("g"); } public int test(){ System.out.println("i"); return 1; } public static int method(){ System.out.println("j"); return 1; } public static void main(String[] args) { Dog dog = new Dog(); System.out.println(); Dog dog1 = new Dog(); } }
执行这段main程序,会输出什么?
答案是
eafjicbhig
icbhigurl
为了方便你们一个个细节去理解, 我换一种方式去提问。
Q: 何时会进行静态变量的赋值和静态代码块的执行?
A:spa
- 第一次建立某个类或者某个类的子类的实例
- 访问类的静态变量、调用类的静态方法
- 使用反射方法forName
- 调用主类的main方法(本例子的第一次静态初始化其实属于这个状况,调用了Dog的main方法)
注: 类初始化只会进行一次, 上面任何一种状况触发后,以后都不会再引发类初始化操做。
Q:初始化某个子类时,也会对父类作静态初始化吗?顺序呢?
A:若是父类以前没有被静态初始化过,那就会进行, 且顺序是先父类再子类。 后面的非静态成员初始化也是如此。
因此会先输出eafj。.net
Q: 为何父类的method不会被子类的method重写?
A: 静态方法是类方法,不会被子类重写。毕竟类方法调用时,是一定带上类名的。3d
Q: 为何第一个输出的是e而不是a?
A: 由于类变量的显示赋值代码和静态代码块代码按照从上到下的顺序执行。
Animal的静态初始化过程当中,method的调用在static代码块以前,因此先输出e再输出a。
而Dog的静态初始化过程当中,method的调用在static代码块以后,所以先输出f,再输出jcode
Q: 没有在子类的构造器中调用super()时,也会进行父类对象的实例化吗?
A: 会的。会自动调用父类的默认构造器。 super()主要是用于须要调用父类的特殊构造器的状况。
所以会先进行Animal的对象实例化,再进行Dog的对象实例化对象
Q: 构造方法、成员显示赋值、非静态代码块(即输出c和h的那2句)的顺序是什么?
A:
- 成员显示赋值、非静态代码块(按定义顺序)
- 构造方法
所以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某个类的数组时,会引起类初始化吗?
像下面输出什么
public class Test { static class A{ public static int a = 1; static{ System.out.println("initA"); } } public static void main(String[] args) { A[] as = new A[5]; } }
A:
new数组时,不会引起类初始化。
什么都不输出。
Q:引用类的final静态字段,会引起类初始化吗?
像下面输出什么?
public class Test { static class A{ public static final int a = 1; static{ System.out.println("initA"); } } public static void main(String[] args) { System.out.println("A.a=" + A.a); } }
A: 不会引起。
不会输出initA。 去掉final就会引起了。
(注意这里必须是基本类型常量, 若是是引用类型产量,则会引起类初始化)
Q:子类引用了父类的静态成员,此时子类会作类初始化嘛?
以下会输出什么
public class Test { static class A{ public static int a = 1; static{ System.out.println("initA"); } } static class B extends A{ static { System.out.println("initB"); } } public static void main(String[] args) { System.out.println("B.a=" + B.a); } }
A:
子类不会初始化。
打印initA,却不会打印initB。
类加载器
双亲委派
类加载时的双亲委派模型,不知道能怎么出题。。。反正就记得优先去父类加载器中看类是否能加载。
就贴个图吧:
注意,上面的图有问题。
Bootsrap不是ClassLoader的子类,他是C++编写的。
而ExtClassLoader和AppClassLoader都是继承自ClassLoader的
Q:java中, 是否类和接口的包名和名字相同, 那么就必定是同一个类或者接口?
A:错误。
1个jvm中, 类和接口的惟一性由 二进制名称以及它的定义类加载器 共同决定。
所以2个不一样的加载器加载出来相同的类或接口时, 其实是不一样的。