类的初始化与实例化

关于

最近一直在看关于类初始化与实例化的知识,对类加载机制理解很混乱,网上也是各种例子,但是还是没有搞懂两者区别,我自己测试了一下,记录一下测试结果。

类的初始化

这里引用网上的通用解答:

类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载七个阶段。其中验证、准备、解析3个部分统称为连接。类加载的过程包括了加载、验证、准备、解析、初始化五个阶段。而虚拟机规范严格规定了有且只有5种情况必须立即对类进行初始化:

第一种:遇到new、getstatic、putstatic、invokestatic这四条字节码指令时,如果类还没有进行过初始化,则需要先触发其初始化。生成这四条指令最常见的Java代码场景是:使用new关键字实例化对象时、读取或设置一个类的静态字段(static)时(被static修饰又被final修饰的,已在编译期把结果放入常量池的静态字段除外)、以及调用一个类的静态方法时。
第二种:使用Java.lang.refect包的方法对类进行反射调用时,如果类还没有进行过初始化,则需要先触发其初始化。
第三种:当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
第四种:当虚拟机启动时,用户需要指定一个要执行的主类,虚拟机会先执行该主类。
第五种:当使用JDK1.5支持时,如果一个java.langl.incoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。
虚拟机规定有且只有这5种情况才会触发类的初始化,这5中场景中的行为称为对一个类进行主动引用,除此之外所有引用类的方式都不会触发其初始化,称为被动引用。

下面来看看初始化阶段类都做了什么。

先看下面这个例子

class A extends B{

    A(){
        System.out.println("A构造方法");
    }
    {
        System.out.println("A普通代码块");
    }
    static{
        System.out.println("A静态代码块");
    }
    public static void main(String[] args){
        System.out.println("A主函数");
    }
    static{
        System.out.println("A-static-2");
    }
}
class B{
    B(){
        System.out.println("B构造方法");
    }
    {
        System.out.println("B普通代码块");
    }
    static{
        System.out.println("B静态代码块");
    }
}

执行结果:

B静态代码块
A静态代码块
A-static-2
A主函数

先说说加载顺序吧,在这里只有初始化,首先A类中有main方法,程序运行时会初始化A类,由于A类继承B类,所以先初始化B类,执行B类静态代码块:“B静态代码块”,加载完B类静态块后加载A类静态代码块:”A静态代码块“,”A-static-2“,至此类加载完毕,执行main方法:“A主函数”。

修改上例中A类的main方法

class A extends B{
    A(){
        System.out.println("A构造方法");
    }
    {
        System.out.println("A普通代码块");
    }
    static{
        System.out.println("A静态代码块");
    }
    public static void main(String[] args){
        A a = new A();
    }
    static{
        System.out.println("A-static-2");
    }
}
class B{
    B(){
        System.out.println("B构造方法");
    }
    {
        System.out.println("B普通代码块");
    }
    static{
        System.out.println("B静态代码块");
    }
}

运行结果:

B静态代码块
A静态代码块
A-static-2
B普通代码块
B构造方法
A普通代码块
A构造方法

在这里出现了类的初始化与实例化,同样先说下加载顺序,首先A类中有main方法,程序运行时先初始化A类,由于A类继承B类,所以先初始化B类,执行B类静态代码块:“B静态代码块”,加载完B类静态块后加载A类静态代码块:“A静态代码块”,“A-static-2”,至此类加载完毕;然后开始执行main方法,A类的实例化,加载B类非静态块与构造方法:“B普通代码块”,“B构造方法”,加载A类非静态块与非静态构造方法:“A普通代码块”,“A构造方法”,至此,类实例化结束。

类的实例化

引用度娘的原话:在面向对象的编程中,通常把类创建对象的过程称为实例化。
类的实例化通常用关键字new实现,当然还有其他两种这里就不说了。

再看下面这个例子

class A extends B{
    A(){
        System.out.println("A构造方法");
    }
    {
        System.out.println("A普通代码块");
    }
    static{
        new A();
        System.out.println("A静态代码块");
    }
    public static void main(String[] args){
        System.out.println("A主函数");
    }
    static{
        System.out.println("A-static-2");
    }
}
class B{
    B(){
        System.out.println("B构造方法");
    }
    {
        System.out.println("B普通代码块");
    }
    static{
        System.out.println("B静态代码块");
    }
}

运行结果:

B静态代码块
B普通代码块
B构造方法
A普通代码块
A构造方法
A静态代码块
A-static-2
A主函数

为什么是这个结果,相信你一定很怀疑,同样的,我一开始也很奇怪,在初始化时遇到实例化,而实例化必需让类先初始化,那么,这样不是死循环吗?相信通过一开始的两个例子,一会发现答案。
好了,先说下类加载顺序,首先A类中有main方法,程序运行时初始化A类,由于A类继承B类,所以先初始化B类,执行B类静态代码块:“B静态代码块”,加载完B类静态块后加载A类静态代码块:new A(),这里需要注意了,在实例化A类的时候会直接中断类的初始化,而实例化A类的顺序为: 加载B的非静态块:“B普通代码块”,加载B的构造方法:“B构造方法”,加载A的非静态代码块:“A普通代码块”,加载A的构造方法:“A构造方法”,至此,类实例化完毕,恢复类初始化,继续加载A静态块:“A静态代码块”,“A-static-2”,加载A主函数:“A主函数”,至此,类初始化完毕。

总结

从上面几个例子可以看出,在类的实例化时,必须先对类进行初始化,而类的初始化仅仅是加载了类的静态块和静态方法。所以我认为,类的初始化和实例化可以拆分为两个不同的功能(或许本来就是两个不同的概念),类初始化是加载静态成员变量、静态块和静态方法,而类的实例化是加载非静态成员变量、非静态代码块和构造方法(普通方法不加载),当初始化遇到实例化时,实例化优先,实例化结束后继续进行初始化。这里还有一个疑问,类初始化进行到一半进行实例化,在初始化没有完成的情况下,为什么能够实例化成功,这个问题还需要慢慢深究。关于类初始化与实例化,此次是我进行的一个小测试,因为技术不深,希望各位技术大牛能够指正!

参考:Java中类的初始化

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值