类的加载过程
一个JAVA从加载到被卸载的过程中被JVM分为五个阶段
加载->链接(验证+准备+解析)->初始化->使用->卸载
2、类的加载过程
(1)加载
首先通过一个类的全限定名(即包名和类名)来获取此类的二进制字节流;其次将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;最后在java堆中生成一个代表这个类的Class对象,作为方法区这些数据的访问入口。总的来说就是查找并加载类的二进制数据。
(2)链接
验证:确保被加载类的正确性;
准备:为类的静态变量分配内存,并将其初始化为默认值;
解析:把类中的符号引用转换为直接引用;
(3)为类的静态变量赋予正确的初始值
3、类的初始化
(1)类什么时候才被初始化
1)创建类的实例,也就是new一个对象
2)访问某个类或接口的静态变量,或者对该静态变量赋值
3)调用类的静态方法
4)反射(Class.forName(“com.lyj.load”))
5)初始化一个类的子类(会首先初始化子类的父类)
6)JVM启动时标明的启动类,即文件名和类名相同的那个类
(2)类的初始化顺序
1)如果这个类还没有被加载和链接,那先进行加载和链接
2)假如这个类存在直接父类,并且这个类还没有被初始化(注意:在一个类加载器中,类只能初始化一次),那就初始化直接的父类(不适用于接口)
3)加入类中存在初始化语句(如static变量和static块),那就依次执行这些初始化语句。
4)总的来说,初始化顺序依次是:(静态变量、静态初始化块)–>(变量、初始化块)–> 构造器;
如果有父类,则顺序是:父类static方法 –> 子类static方法 –> 父类构造方法- -> 子类构造方法
1.
继承的加载顺序
由于static块会在首次加载类的时候执行,因此下面的例子就是用static块来测试类的加载顺序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | class A{ static { System.out.println( "A static" ); } } class B extends A{ static { System.out.println( "B static" ); } } class C extends B{ private static D d = new D(); static { System.out.println( "C static" ); } } class D{ static { System.out.println( "D static" ); } } public class ExtendTest { public static void main(String[] args) { C c = new C(); } }A static B static D static C static |
所有的变量初始化完,才会执行构造方法
在类的加载过程中,只有内部的变量创建完,才会去执行这个类的构造方法。
2、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | package xing.test.thinking.chap7; class A2{ B2 b2 = new B2(); static { System.out.println( "A static" ); } public A2() { System.out.println( "A2()" ); } } class B2{ C2 c2 = new C2(); D2 d2 = new D2(); static { System.out.println( "B static" ); } public B2() { System.out.println( "B2()" ); } } class C2{ static { System.out.println( "C static" ); } public C2() { System.out.println( "C2()" ); } } class D2{ static { System.out.println( "D static" ); } public D2() { System.out.println( "D2()" ); } } public class VarTest { public static void main(String[] args) { A2 a2 = new A2(); } } A static B static C static C2() D static D2() B2() A2() |
静态成员与普通成员类的加载区别
在类的加载过程中,静态成员类的对象,会优先加载;而普通成员类的对象则是使用的时候才回去加载。
3.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | package xing.test.thinking.chap7; class A3{ B3 b3 = new B3(); static C3 c4 = new C3(); static { System.out.println( "A3" ); } } class B3{ static { System.out.println( "B3" ); } } class C3{ static { System.out.println( "C3" ); } } public class StaticTest { public static void main(String[] args) { A3 a3 = new A3(); }<br><br><br><br>C3 <br>A3<br>B3 |
第一点,所有的类都会优先加载基类
第二点,静态成员的初始化优先
第三点,成员初始化后,才会执行构造方法
第四点,静态成员的初始化与静态块的执行,发生在类加载的时候。
第四点,类对象的创建以及静态块的访问,都会触发类的加载。
4、类的构造顺序package xing.test.thinking.chap8;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | class A{ public A() { System.out.println( "A" ); } } class B extends A{ public B() { System.out.println( "B" ); } } class C extends B { private D d1 = new D( "d1" ); private D d2 = new D( "d2" ); public C() { System.out.println( "C" ); } } class D { public D(String str) { System.out.println( "D " +str); } } public class ExtendTest { public static void main(String[] args) { C c = new C(); } }A B D d1D d2C |
因此可以得出结论:
- 首先会调用基类的构造方法
- 其次,调用成员的构造方法
- 最后,调用自己的构造方法
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理