Java类的加载时机

2022-01-24 0 262 百度已收录

必须初始化的四种情况

有四种情况类是必须要进行初始化的,对于这四种情况原文描述如下:

但是对于初始化阶段,虚拟机规范则是严格规定了有且只有4种情况必须立即对类进行初始化,而加载、验证、准备自然需要在此之前开始。

  • 1:遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令最常见的java代码场景是:使用new关键字实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
  • 2:使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
  • 3:当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
  • 4:当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。

以上四点我们一一用代码来验证,第一点里面说到了四种初始化的场景,分别是:

  • ①用new关键字实例化对象
  • ②读取类静态字段
  • ③设置类的静态字段
  • ④调用一个类的静态方法

在验证之前需要达成一个共识:虚拟机在初始化类时会执行static语句块中的操作,因此我们可以根据静态语句块中的代码是否执行了来判断类是否加载。为此我创建了一个SubClass类

package com.test.jvm.classloading;

/**
 * @author fc
 */
public class SubClass {
    static{
        System.out.println(\"子类初始化\");
    }
    public static int a = 10;

    public static int getA(){
        return a;
    }
}

在main方法中分别执行(每次执行一条)以下四条代码来模拟上面四个场景

package com.test.jvm.classloading;

/**
 * @author fc
 */
public class Main {
    public static void main(String[] args) {
        SubClass subClass = new SubClass();
        System.out.println(SubClass.a);
        SubClass.getA();
        SubClass.a = 30;
    }
}

结果不出所料,输出结果都包含\"子类初始化\",说明以上四种方式确实可以会触发类的初始化。

接下来看第二点,对类进行反射调用时会触发类的初始化

package com.test.jvm.classloading;

/**
 * @author fc
 */
public class Main {
    public static void main(String[] args) throws ClassNotFoundException {
        Class.forName(\"com.test.jvm.classloading.SubClass\");
    }
}

以上的反射调用同样正常输出了\"子类初始化\"。

第三点如果父类没有进行初始化,则要先触发父类的初始化,再创建一个父类,并且让之前的子类继承父类

package com.test.jvm.classloading;

/**
 * @author fc
 */
public class SuperClass {
    static {
        System.out.println(\"父类初始化\");
    }
    public static int b = 20;
}
package com.test.jvm.classloading;

/**
 * @author fc
 */
public class SubClass extends SuperClass {
    static{
        System.out.println(\"子类初始化\");
    }
    public static int a = 10;

    public static int getA(){
        return a;
    }
}

这时我们再次执行上面的main方法里面的任意一条测试语句,这时发现在原来的输出\"子类初始化\"前输出了\"父类初始化\",说明了两点:①父类同样会初始化;②父类会先于子类初始化。

第四点虚拟机会先初始化包含main方法的主类,这时我们在主类中加入静态代码块

package com.test.jvm.classloading;

/**
 * @author fc
 */
public class Main {
    static {
        System.out.println(\"初始化主类\");
    }
    public static void main(String[] args) throws ClassNotFoundException {
        SubClass subClass = new SubClass();
    }
}

可以看到输出结果如下,完全印证了第四点。

不主动进行初始化

而对于不会主动进行初始化的情况在该书中也有以下几种情况

第一种是通过子类类名调用父类静态代码(包括静态方法和静态变量)不会进行初始化,以下也通过代码进行说明

package com.test.jvm.classloading;

/**
 * @author fc
 */
public class Main {
    public static void main(String[] args) throws ClassNotFoundException {
        System.out.println(SubClass.b);
    }
}

输出如下,可以看到只初始化了父类而没有初始化子类。

第二种是通过数组来创建对象不会触发此类的初始化

package com.test.jvm.classloading;

/**
 * @author fc
 */
public class Main {
    public static void main(String[] args) throws ClassNotFoundException {
        SuperClass[] supers = new SuperClass[10];
    }
}

输出为空。

第三种是调用final修饰的常量不会触发类的初始化,为此我在父类中加了一个常量

package com.test.jvm.classloading;

/**
 * @author fc
 */
public class SuperClass {
    static {
        System.out.println(\"父类初始化\");
    }
    public static int b = 20;

    public final static String STATE = \"常量\";
}
package com.test.jvm.classloading;

/**
 * @author fc
 */
public class Main {
    public static void main(String[] args) {
        System.out.println(SuperClass.STATE);
    }
}

可以看到输出结果只是打印了常量的值,并没有初始化这个类。

补充

到这里对于书中描述的类的加载时机都已经用例子说明了,接下来展示一个在博主Boblim那看到的一个例子

/**
 * @author fc
 */
class SingleTon {
    private static SingleTon singleTon = new SingleTon();
    public static int count1;
    public static int count2 = 0;

    private SingleTon() {
        count1++;
        count2++;
    }

    public static SingleTon getInstance() {
        return singleTon;
    }
}

public class Test {
    public static void main(String[] args) {
        SingleTon.getInstance();
        System.out.println(\"count1=\" + SingleTon.count1);
        System.out.println(\"count2=\" + SingleTon.count2);
    }
}

输出count1=1,count2=0,关于为什么会输出这个结果在那篇链接的博客已经做了详细的说明,同时这个输出结果也很好地佐证了下面这句话

类构造器<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的。

正是给类变量赋值时是按照顺序进行的,所以上面count2又会被重新赋值为0,才导致这个输出结果。

以上所述是小编给大家介绍的Java类的加载时机,希望对大家有所帮助。在此也非常感谢大家对网站的支持!

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)
如需 WordPress 优化加速、二次开发、网站维护、企业网站建设托管等服务,可联系我购买付费服务:点此联系我 | 近期站内热门福利推荐:

:本文采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可, 转载请附上原文出处链接。
1、本站提供的源码不保证资源的完整性以及安全性,不附带任何技术服务!
2、本站提供的模板、软件工具等其他资源,均不包含技术服务,请大家谅解!
3、本站提供的资源仅供下载者参考学习,请勿用于任何商业用途,请24小时内删除!
4、如需商用,请购买正版,由于未及时购买正版发生的侵权行为,与本站无关。
5、本站部分资源存放于百度网盘或其他网盘中,请提前注册好百度网盘账号,下载安装百度网盘客户端或其他网盘客户端进行下载;
6、本站部分资源文件是经压缩后的,请下载后安装解压软件,推荐使用WinRAR和7-Zip解压软件。
7、如果本站提供的资源侵犯到了您的权益,请邮件联系: 1798582342@qq.com 进行处理!

文章版权及转载声明

作者:有趣本文地址:https://www.zyhao.net/297954.html最后更新时间为 2022年02月06日 星期日 14:29:21
文章转载或复制请以超链接形式并注明来源出处 有趣源码
声明:某些文章或资源具有时效性,若有 错误 或 所需下载资源 已失效,请联系客服QQ:11210980

有趣源码,优质资源分享网

常见问题
  • 本站所有资源版权均属于原作者所有,均只能用于参考学习,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担
查看详情
  • 最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,建议提前注册好百度网盘账号,使用百度网盘客户端下载
查看详情

相关文章