总结Java里类的加载顺序_面试高频考点之一

java里面类的加载顺序(秋招必考的知识点)

知识点总结
总结Java里类的加载顺序_面试高频考点之一
总结Java面向对象特点及基础_面试高频考点之一
总结Java集合类_面试考点之一
总结Java(23模式)工厂模式_面试高频考点之一
在这里插入图片描述

简介:对于一个java程序的执行流程,首先编写生成java的源代码文件,接着调用java的编译器对源代码进行解析,其中包括语法分析、语义分析、词法分析等等一系列的过程,如下图。在这里插入图片描述
编译结束之后生成java的class字节码文件,字节码也定义了一系列的相关标准。最后将字节码运到JVM中去解析为可在各个平台所运行。接下来讲讲本篇文章的重点——类加载器(类装载子系统)。要想弄明白类的加载顺序及过程,首先来写两个例子。
在这里插入图片描述
类装载过程
在这里插入图片描述

1.将class文件加载到类加载子系统
2.开辟一个包含 堆、方法区、栈、本地方法栈和程序计数器的空间。
3.字节码引擎开始执行
静态块,加载步骤
一.装载

1.通过类型的完全限定名,产生一个代表该类型的二进制数据流
2.解析这个二进制数据流为方法区内的内部数据结
3.构创建一个表示该类型的java.lang.Class类的实例
另外如果一个类装载器在预先装载的时遇到缺失或错误的class文件,它需要等到程序首次主动使用该类时才报告错误。

二.连接

1.验证,确认类型符合Java语言的语义,检查各个类之间的二进制兼容性(比如final的类不用拥有子类等),另外还需要进行符号引用的验证。
2.准备,Java虚拟机为类变量分配内存,设置默认初始值。
3.解析(可选的) ,在类型的常量池中寻找类,接口,字段和方法的符号引用,把这些符号引用替换成直接引用的过程。

三.初始化

1.当创建某个类的新实例时(如通过new或者反射,克隆,反序列化等)
2.当调用某个类的静态方法时
3.当使用某个类或接口的静态字段时
4.当调用Java API中的某些反射方法时,比如类Class中的方法,或者java.lang.reflect中的类的方法时
5.当初始化某个子类时
6.当虚拟机启动某个被标明为启动类的类(即包含main方法的那个类)
Java编译器会收集所有的类变量初始化语句和类型的静态初始化器,将这些放到一个特殊的方法中:clinit。

例子

父类Numberr

package com.hello.world;

public class Numberr {
    public  static final int num = 999999;
    static{//静态代码块
        System.out.println("我是父类静态代码块");
    }
    {//普通代码块
        System.out.println("我是父类代码块");
    }
    public Numberr(){
        System.out.println("我是父类构造方法");
    }
}

子类BigNumber

package com.hello.world;

public class BigNumber extends Numberr{
    private static Numberr bg = new BigNumber();//父类引用指向子类对象,运行时多态的表现形式之一
    private static int max;//静态成员变量
    private static int small;//静态成员变量
    public  int number1 = 1;//实例变量
    public static  int number2 = 1;
    public static  int number3 = 1;
    static{//静态代码块
        max = 0x7fffffff;
        small = 0x80000000;
        System.out.println("我是子类静态代码块");
    }
    {//代码块
        number3++;
        System.out.println("我是子类代码块");
    }
    public BigNumber(){//子类构造方法
        super();//父类对象的引用必须放在第一行,不写会默认调用无参父类构造方法
        number1++;
        number2++;
        System.out.println("我是子类的构造方法");
    }
        public void show(){
        System.out.println("max:"+max+"small:"+small+"number1:"+number1+"number2:"+number2+"number3:"+number3);
    }

}

测试类

package com.hello.world;

public class Test {
    public static void main(String[] args) {
        BigNumber bgn = new BigNumber();//实例化一个对象
        Numberr nub = new BigNumber();//实例化父类的一个对象,与直接创建父类对象的不同点在于会调用子类重写父类的方法
        Numberr nu = new Numberr();//实例化父类对象
        bgn.show();
    }
}

先看结果在分析

实例化父类对象,显示结果如下:
在这里插入图片描述
分析:
由执行结果分析可以很简单的得出结论,静态代码块最先执行,普通代码块优先于构造方法先执行。其实不仅如此静态代码块优先于主方法先进行加载。而代码块只有创建或声明一个对象的时候,该类的代码块才会被加载。

实例化父类引用指向子类对象结果如下:
在这里插入图片描述
分析
在父类的基础上加入了父类引用指向子类对象,属于向上转型小类型转大类型自动转换,此时的加载的顺序是父类的静态代码块接着是子类的静态代码块,父类代码块,父类代码块,父类构造方法,子类代码块,子类的构造方法的一个顺序。由此可以看出创建子类时会优先调用父类的静态代码块、父类代码块和构造方法。且普通的代码块只会在构造方法之前调用。

同时实例化父类对象和子类对象时,如下图:
在这里插入图片描述
分析:
首先声明的是子类对象,与先前的情况一致。不同点在于父类代码块和父类构造方法加载了两次,而静态代码块只加载了一次。同时也可以说明,静态代码块只会加载一次,执行完便销毁。

将子类里的静态变量的注释去掉并输出信息如下(去掉所有注释):
在这里插入图片描述
分析:
发现和先前的情况都相同,特别是开头的父类静态代码块、父类的代码块、父类的构造方法是连着一起执行的这是为什么呢?其实不然这种情况出现在子类中定义了 private static Numberr bg = new BigNumber();子类的一个对象,在此之前并没有静态的代码块,所以不会在父类静态代码块之后去执行子类的静态代码块,而是按照创建子类对象的顺序去走,先是父类的静态代码块在是父类的代码块、父类构造方法,在是子类的代码块,子类构造方法。此时的 private static Numberr bg = new BigNumber();算加载完成。这样说明一个问题,静态变量和静态代码块的加载优先级相同,谁在前面谁会被优先执行。之后才会加载子类的静态代码块,在此之前都没有加载main主方法,从下图可以看出:

在这里插入图片描述
在此之后进入main主方法依次加载,和之前完全相同。

总结

普通类无继承关系的加载顺序是静态代码-块代码块-构造方法
含有继承的类加载顺序父类静态代码块-子类静态代码块-父类代码块-父类构造方法-子类代码块-子类构造方法
含有静态成员变量为子类对象的类加载顺序是父类静态代码块-父类的代码块-父类构造方法-子类代码块-子类构造方法-子类的静态代码块在此之前还未执行main主方法。
被static修饰的变量和代码块的加载优先级相同,且优先于主方法就会被提前加载,它会在类的初始化的时候执行一次,执行完之后便销毁,它仅能初始化类变量,static修饰的成员变量。

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

牛牛最爱喝兽奶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值