初始化和实例化的Java顺序

时间:2014-04-15 20:07:56

标签: java

我正在尝试将JVM中的初始化和实例化过程拼凑在一起,但JLS对一些细节有点迟钝,所以如果有人想清除一些细节,我们将不胜感激。这是我迄今为止所能想到的。

初始化

  1. 递归初始化类的静态最终变量及其作为编译时常量的接口。

  2. 退出以文本顺序处理静态块和静态字段的递归。

  3. 实例化

    1. 递归初始化作为编译时常量的类的最终实例变量。

    2. 退出递归处理文本顺序中的非静态块和实例字段,在返回时将它们添加到构造函数中。


    3. 好的,现在问题。

      1. 是按声明顺序处理的接口吗?

      2. 是在单独的递归堆栈中处理的接口吗?

        a)如果是,是否在超类之前或之后处理接口?

        b)如果是,我在推断其中一个或其他(接口或超类)在其他编译时常量之前初始化其非编译时常量字段时是否正确。

      3. 调用nondefault super()构造函数在此过程中扮演什么角色?

      4. 我的任何结论都错了吗?

      5. 我错过了其他任何关键细节吗?

2 个答案:

答案 0 :(得分:43)

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

班级初始化

通过分配编译时常量字段,然后递归初始化超类(如果尚未初始化),然后处理静态初始值设定项(包括静态初始值设定项),在first access上初始化类或接口。不是编译时常量的字段)。

正如您所注意到的,类的初始化本身并不会触发它实现的接口的初始化。 因此,首次访问接口时,通常通过读取非编译时间常量的字段来初始化接口。在评估初始化程序期间可能会发生此访问,从而导致递归初始化。

值得注意的是,访问作为编译时常量的字段不会触发初始化,因为这些字段是在compile time评估的:

  

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

     

如果这样的字段是静态的,那么二进制文件中的代码中不应该存在对该字段的引用,包括声明该字段的类或接口。这样的字段必须总是看似已经初始化(§12.4.2);

。必须永远不要观察该字段的默认初始值(如果不同于V)。

     

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

对象初始化

通常通过评估类实例创建表达式来初始化对象whenever a new object is created。其过程如下:

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

  2.   
  3. 如果此构造函数以同一个类中的另一个构造函数的显式构造函数调用(第8.8.7.1节)开头(使用此方法),则使用这五个相同步骤计算参数并以递归方式处理该构造函数调用。如果该构造函数调用突然完成,则此过程突然完成,原因相同;否则,继续步骤5.

  4.   
  5. 此构造函数不以同一类中另一个构造函数的显式构造函数调用开头(使用此方法)。如果此构造函数用于Object以外的类,则此构造函数将以超类构造函数的显式或隐式调用开始(使用super)。使用这五个相同的步骤评估参数并递归处理超类构造函数调用。如果该构造函数调用突然完成,则此过程突然完成,原因相同。否则,请继续执行步骤4.

  6.   
  7. 为此类执行实例初始值设定项和实例变量初始值设定项,将实例变量初始值设定项的值按从左到右的顺序分配给相应的实例变量,在这些顺序中它们以文本方式出现在源代码中类。如果执行任何这些初始值设定项导致异常,则不会处理其他初始化程序,并且此过程会突然完成同样的异常。否则,请继续步骤5.

  8.   
  9. 执行此构造函数的其余部分。如果执行突然完成,则此过程突然完成,原因相同。否则,此过程正常完成。

  10.   

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

答案 1 :(得分:2)

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

<强> InstanceCreateStepTest.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