为了账号安全,请及时绑定邮箱和手机立即绑定

Java初始化和实例化顺序

Java初始化和实例化顺序

翻阅古今 2019-07-15 16:52:54
Java初始化和实例化顺序我试图将JVM中的初始化和实例化过程拼凑在一起,但是JLS在一些细节上有点迟钝,所以如果有人介意澄清一些细节,我们将不胜感激。到目前为止,这就是我所能找到的。初始化递归初始化类的静态最终变量及其编译时间常数的接口。退出递归,按文本顺序处理静态块和静态字段。实例化递归初始化编译时间常数的类的最终实例变量。退出递归处理,按文本顺序处理非静态块和实例字段,在返回时将它们放在构造函数的前面。好了,现在开始提问。接口是否按声明顺序处理?接口是否在单独的递归堆栈中处理?如果是,是否在超类之前或之后处理接口?(B)如果是,我是否正确地推断其中一个或另一个(Interface或Superclass)在其他编译时常量之前初始化了它的非编译时常数字段。在这个过程中,调用非默认的超级()构造函数扮演什么角色?我的结论有错吗?我有没有遗漏其他关键细节?
查看完整描述

2 回答

?
慕标5832272

TA贡献1966条经验 获得超4个赞

区分类的初始化和对象的初始化非常重要。

类初始化

初始化类或接口。第一次访问,通过分配编译时间常数字段,然后递归初始化超类(如果尚未初始化),然后处理静态初始化器(其中包括非编译时间常数的静态字段的初始化程序)。

正如您已经注意到的,类的初始化本身并不会触发它实现的接口的初始化。因此,接口在第一次访问时被初始化,通常是通过读取非编译时间常数的字段进行初始化。..此访问可能发生在初始化器的计算过程中,从而导致递归初始化。

还值得注意的是,初始化不是通过访问作为编译时间常量的字段来触发的,因为这些字段是在编译时间:

对常量变量(§4.12.4)的字段的引用必须在编译时解析为由常量变量的初始化器表示的值V。

如果这样的字段是静态的,那么在二进制文件中的代码中不应该存在对该字段的引用,包括声明该字段的类或接口。这样的字段必须始终看起来已经初始化(§12.4.2);字段的缺省初始值(如果与V不同)绝不能被观察到。

如果这样的字段是非静态的,那么除了包含字段的类之外,二进制文件中的代码中不应该存在对字段的引用。(它将是一个类,而不是一个接口,因为接口只有静态字段。)类应该有代码,以便在实例创建期间将字段的值设置为V(§12.5)。

对象初始化

对象被初始化。每当创建新对象时,通常通过计算类实例创建表达式。这方面的进展如下:

  1. 将构造函数的参数分配给此构造函数调用的新创建的参数变量。

  2. 如果该构造函数以同一个类中的另一个构造函数的显式构造函数调用(§8.8.7.1)开始(使用此方法),则使用这五个步骤递归地计算参数和处理构造函数调用。如果构造函数调用突然完成,则此过程出于同样的原因突然完成;否则,继续执行步骤5。

  3. 此构造函数不以同一类中的另一个构造函数的显式构造函数调用开始(使用此方法)。如果此构造函数用于对象以外的类,则此构造函数将以超类构造函数的显式或隐式调用(使用超级)开始。使用这五个步骤递归地评估超类构造函数调用的参数和过程。如果构造函数调用突然完成,则此过程出于同样的原因突然完成。否则,继续执行步骤4。

  4. 为该类执行实例初始化器和实例变量初始化器,将实例变量初始化器的值按从左到右的顺序分配给相应的实例变量,这些变量在类的源代码中以文本形式出现。如果执行这些初始化程序中的任何一个都会导致异常,那么将不再处理其他初始化器,并且该过程在相同的异常下突然完成。否则,继续执行步骤5。

  5. 执行此构造函数主体的其余部分。如果该执行突然完成,则此过程出于同样的原因突然完成。否则,此过程将正常完成。

正如我们在步骤3中所看到的,对超级构造函数的显式调用的存在只是改变了调用哪个超类构造函数。


查看完整回答
反对 回复 2019-07-15
?
天涯尽头无女友

TA贡献1831条经验 获得超9个赞

下面是一个在对象创建过程中打印每个步骤顺序的示例。

java:

import javax.annotation.PostConstruct;/**
 * Test steps of instance creation.
 * 
 * @author eric
 * @date Jan 7, 2018 3:31:12 AM
 */public class InstanceCreateStepTest {
    public static void main(String[] args) {
        new Sub().hello();
        System.out.printf("%s\n", "------------");
        new Sub().hello();
    }}class Base {
    static {
        System.out.printf("%s - %s - %s\n", "base", "static", "block");
    }
    {
        System.out.printf("%s - %s - %s\n", "base", "instance", "block");
    }

    public Base() {
        System.out.printf("%s - %s\n", "base", "constructor");
    }

    @PostConstruct
    public void init() {
        System.out.printf("%s - %s\n", "base", "PostConstruct");
    }

    public void hello() {
        System.out.printf("%s - %s\n", "base", "method");
    }}class Sub extends Base {
    static {
        System.out.printf("%s - %s - %s\n", "sub", "static", "block");
    }
    {
        System.out.printf("%s - %s - %s\n", "sub", "instance", "block");
    }

    public Sub() {
        System.out.printf("%s - %s\n", "sub", "constructor");
    }

    @PostConstruct
    public void init() {
        System.out.printf("%s - %s\n", "sub", "PostConstruct");
    }

    @Override
    public void hello() {
        // super.hello();
        System.out.printf("%s - %s\n", "sub", "method");
    }}

处决:

只需调用main方法,然后检查输出。

小贴士:

  • 标记的方法

    @PostConstruct

    不会被调用,除非您在某个容器中调用它,例如

    Spring-boot

    ,因为它依赖于那些容器来实现注释,如

    @PostConstruct.


查看完整回答
反对 回复 2019-07-15
  • 2 回答
  • 0 关注
  • 849 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信