JVM 原理四:接口初始化规则与类加载器准备阶段和初始化阶段的重要意义

>>号外:关注“Java精选”公众号,回复“2021面试题”关键词,领取全套500多份Java面试题文件。

连接:先看一段代码:

public class MyTest5 {

    public static void main(String[] args) {
        System.out.println(MyChild5.b);
    }
}

interface MyParent5{
    public static final int a = 5;
}

interface MyChild5 extends  MyParent5{
    public static final int b = 6;
}

这段代码相信运行结果大家都能一目了然,那么父接口MyParent5和MyChild5 是否会初始化呢?由于在接口当中不能定义静态代码块, 因此我们把MyParent5的class文件删除再去运行:

由此得出当一个接口初始化时并不要求其父接口完成了初始化

我们在进一步,这时删除MyChild5 再去运行:

再接着我们这样写代码,然后删除父接口:


由此就可以得出,子接口的常量只有在运行期间才能确定的,并不会放到调用类的常量池当中,这时会触发父接口的初始化。

我们再次反过来实验:


这次我们删除了子接口,但是父接口的常量只有在运行期间才会确认,这个时候会触发子类的初始化。

还有下边这种情况:

总结:

**当一个接口初始化时并不要求其父接口完成了初始化

只有在真正用到父接口的时候(如引用接口中定义的常量时),才会初始化。** 而类不是这样的,原因就是借口中的变量本来就是final的。

接着我们把MyChild5改为Class的形式,然后删除MyParent5:


发现MyParent5找不到,因此补充结论:

只有使用类的常量的时候不会去加载接口,一般的静态变量,非常量都会加载接口。因为非常量都不会纳入到MyTest5的常量池当中,因此导致接口的初始化。

类加载器准备阶段很初始化阶段的重要意义:

我们写一个单例模式:

public class MyTest6 {
    public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance();
        System.out.println(Singleton.counter1);
        System.out.println(Singleton.counter2);
    }
}

class Singleton{
    public static int counter1;
    public static int counter2=0;
    private static Singleton singleton = new Singleton();
    private Singleton(){
        counter1++;
        counter2++;
    }
    public static Singleton getInstance(){
        return singleton;
    }
}

程序运行结果:

1
1

毫无悬念肯定是1 1 。

接下来我们改一下程序:

public class MyTest6 {
    public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance();
        System.out.println(Singleton.counter1);
        System.out.println(Singleton.counter2);
    }
}

class Singleton{
    public static int counter1;
    private static Singleton singleton = new Singleton();
    private Singleton(){
        counter1++;
        counter2++;
    }
    public static int counter2=0;
    public static Singleton getInstance(){
        return singleton;
    }
}

我们把counter2的位置改变了,那么运行结果是什么呢:

1
0

这个例子能充分体现类的加载阶段和初始化阶段的重要性,我们用第一篇的过程分析一波:

加载:查找并加载类的二进制数据【这个不说了,就是把Singleton的二进制文件放入内存】

连接:

验证:确保被加载类的正确性。【这个也不说了】

准备:为类的静态变量分配内存,并将其初始化为默认值。【此时Singleton的counter1赋予初始化值为0,singleton赋值为null,counter2赋值为0】

解析:把类中的符号引用装换为直接引用。【不说了】

初始化:为类的静态变量赋予正确的初始值。【程序初始化顺序是从上而下,首先是counter1程序员没有对它进行赋值,还是准备赋予的值为0,接着是singleton赋值为new Singleton(),此时会调用构造器,构造器调用完毕counter1=1,counter2=1,然后程序继续初始化,到了 public static int counter2=0;时,静态变量counter2重新赋值为0】

验证:确保被加载类的正确性。【这个也不说了】

准备:为类的静态变量分配内存,并将其初始化为默认值。【此时Singleton的counter1赋予初始化值为0,singleton赋值为null,counter2赋值为0】

解析:把类中的符号引用装换为直接引用。【不说了】

初始化:为类的静态变量赋予正确的初始值。【程序初始化顺序是从上而下,首先是counter1程序员没有对它进行赋值,还是准备赋予的值为0,接着是singleton赋值为new Singleton(),此时会调用构造器,构造器调用完毕counter1=1,counter2=1,然后程序继续初始化,到了 public static int counter2=0;时,静态变量counter2重新赋值为0】

作者:魔鬼_

blog.csdn.net/wzq6578702/article/details/79382182

往期精选  点击标题可跳转

JVM 原理三:编译期常量与运行期常量的区别及数组创建本质分析

JVM 原理二:常量的本质含义与反编译及助记符详解

JVM原理一:类加载器深入解析与阶段分解

Java 中统计代码执行耗时,列举 4 种优雅的解决方案

MySQL 分页使用 limit 和 offset 参数为什么会导致执行变慢?

全网可能是最全的 JAVA 日志框架适配、冲突解决方案

数据库在哪些场景下导致索引失效,索引何时会失效?

为什么 Redis 越来越慢了?延迟问题定位排查与分析

Spring 框架中导致 @Transactional 事务注解 3 种失效场景分析及解决方法

放弃 JDK8 中 StringBuilder,使用 StringJoiner 辅助类,真香!

面试时这样回答 Java 应用性能调优,回报是更多 Money!

点个赞,就知道你“在看”!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值