【问题标题】:Java order of Initialization and InstantiationJava 的初始化和实例化顺序
【发布时间】:2014-04-15 20:07:55
【问题描述】:

我正在尝试将 JVM 中的初始化和实例化过程拼凑起来,但 JLS 在一些细节上有点迟钝,所以如果有人愿意澄清一些细节,我们将不胜感激。到目前为止,这是我能够弄清楚的。

初始化

  1. 递归地初始化类的静态最终变量,它的接口是编译时常量。

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

实例化

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

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


好的,现在开始提问。

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

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

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

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

  3. 对非默认 super() 构造函数的调用在此过程中扮演什么角色?

  4. 我的结论有误吗?

  5. 我是否遗漏了任何其他关键细节?

【问题讨论】:

  • 接口没有任何需要初始化的东西。除非你使用这个词的方式与我习惯的不同。
  • @BobDalgleish 接口可能具有公共静态最终字段。如果这些字段是具有初始化程序的对象(例如public static final ArrayList<String> someStrings = new ArrayList<>();),那么这将是初始化的一部分。

标签: java


【解决方案1】:

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

类初始化

first access 上初始化一个类或接口,通过分配编译时常量字段,然后递归初始化超类(如果尚未初始化),然后处理静态初始化程序(包括用于静态字段的初始化程序)不是编译时常量)。

正如您所注意到的,类的初始化本身并不会触发它实现的接口的初始化。 接口在第一次被访问时被初始化,通常是通过读取一个不是编译时间常量的字段。这种访问可能发生在初始化器的评估期间,导致递归初始化。

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

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

如果这样的字段是静态的,那么二进制文件的代码中不应存在对该字段的引用,包括声明该字段的类或接口。这样的字段必须始终显示为已初始化(第 12.4.2 节);绝对不能观察该字段的默认初始值(如果不同于 V)。

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

对象初始化

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

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

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

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

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

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

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

【讨论】:

    【解决方案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 这样的注解。

    【讨论】:

      猜你喜欢
      • 2014-09-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-05-21
      • 1970-01-01
      • 2021-05-07
      • 2013-10-18
      相关资源
      最近更新 更多