记一次Java中存在一种情况----为什么先执行构造方法再执行静态代码块里的内容

Java存在先执行构造方法后执行静态代码块的情况及原因

自己在第一遇到这个问题是在牛课网做Java的选择题的时候,那个时候感觉到很疑惑。然后自己照着上面的内容码了一遍,代码如下:

public class ExtendsTest {
    public static void main(String[] args) {
        new B();
    }
}

class A{
    //这个时候由于在初始化类B的时候类A还没进行初始化,因此要进行A的初始化即执行<cinit>
    //而在这里的时候由于类A的初始化已经被触发了,所以这里就不需要在进行类A的初始化即不需要再执行<cinit>,而是直接执行实例构造方法<init>
    private static A a = new A();
    static{
        b = new A();
        //System.out.println(b);
        System.out.println("A静态块执行");
    }
    public A(){
        System.out.println("A的构造方法执行了");
    }
    private static A b;
}

class B extends A{
    private static B c = new B();
    static {
        System.out.println("B静态块执行");
    }
    public B(){
        System.out.println("B的构造方法执行了");
    }
}

运行结果如下:
运行结果图
从上面的运行结果可以看出,先执行了A的构造方法后面才执行A的静态代码块,原因在哪呢?

下面为个人的理解:

在解答这个问题前我们需要知道一个类是什么时候进行初始化的,或者说是什么情况下会进行初始化(注意这里写的是类的初始化(感兴趣的同学可以去了解一下类的加载机制)),在《深入理解Java虚拟机》一书中提到:对于类的初始化阶段,虚拟机规范是严格规定了有且只有以下五种情况必须立即对类进行初始化(我们这里关注的是第三点):

  1. 使用new关键字实例化对象的时候、读取或设置一个静态变量的时候(被final修饰、已在编译器把结果放入常量池的静态字段除外)、以及调用一个类的静态方法的时候

  2. 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行初始化,则先触发器初始化。

  3. 当初始化一个类的时候,发现其父类还没有进行过初始化,则需要先触发其父类的初始化。

  4. 当虚拟机启动的时候,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个类。

  5. 当使用JDK7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。

到这我们开始进行分析:

从上面的代码可以看出,main方法中new的是一个子类对象,这个时候有第一点可得其会对子列进行初始化,但这个时候看第三点,我们可以知道这个时候需要对父类进行初始化,这里就可以解释父类先于子类之前初始化。

下面我们在说说为什么中为什么会先执行构造方法:

对于父类其中包含两个静态成员和一个静态代码块,在说这个之前我们需要了解类的加载过程:加载->验证->准备->解析->初始化(想详细了解其具体信息的可以再自行百度)。对于加载过程中的静态变量,他们会在准备阶段进行一次初始化,值得注意的是这里的初始化赋的值为其类型对应的零值,而不是源代码中赋的初始值。其真正进行程序中的初始值实在加载的最后一个阶段进行的(即初始化过程),在这个过程如果代码中包含静态变量或者代码块,jvm会利用一个< cinit > 函数对这些静态的东西按照顺序进行初始化(这里的初始化赋值为程序代码中的值)。因此上面的代码中,父类中的第一个静态属性赋值为new A(),在这个类初始化的过程中进行赋值,所以其打印的是构造方法里面的内容,虽然这里使用到了new关键字但由于类A已经进行了加载并在进行初始化因此并不需要再次加载所以这里的new A()通过调用 < init >方法来构造类A的对象。对于与不了解的可以自行查找相关的解释。这里只简单的说以下,< init >相当于构造方法,在每次构造一个对象的时候会执行一次,而< cinit >是为了初始化类的静态成员和代码块的。因此其只会执行一次。

总结

上面的内容完全是个人的理解,希望对你有帮助,如果有存在不正确的地方还望指出。

静态代码块构造块和构造方法Java对象初始化的三种方式,它们各自具有不同的执行顺序和作用。它们的区别如下: 1. 静态代码块是在类被加载执行代码块,它只执行一次静态代码块主要用于类的初始化,如静态变量的初始化等。 2. 构造块是在类定义的代码块,每次创建对象都会执行,不论是调用无参构造函数还是有参构造函数。构造块用于初始化对象,如为对象的成员变量分配内存等。 3. 构造方法也是用于对象的初始化,但是它可以接收参数。构造方法用于为对象的成员变量赋初值。 下面是一个例子,可以帮助理解它们之间的关系: ```java public class BlockTest { static { System.out.println("静态代码块执行"); } { System.out.println("构造执行"); } public BlockTest() { System.out.println("无参构造执行"); } public BlockTest(int a) { System.out.println("有参构造执行"); } public static void main(String[] args) { BlockTest bt1 = new BlockTest(); BlockTest bt2 = new BlockTest(10); } } ``` 运行以上代码,输出结果如下: ``` 静态代码块执行 构造执行 无参构造执行 构造执行 有参构造执行 ``` 可以看出,静态代码块是在类被加载执行的,构造块是每次创建对象都会执行的,而构造方法是用于为对象的成员变量赋初值的。 需要注意的是,虽然静态代码块构造块都用于对象的初始化,但是它们的执行顺序是有区别的。静态代码块只会在类初始化执行一次,而构造块则会在对象创建执行。因此,静态代码块构造执行
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值