Java 初始化和实例化顺序 [英] Java order of Initialization and Instantiation

查看:32
本文介绍了Java 初始化和实例化顺序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

初始化

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

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

实例化

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

  2. 退出递归处理非静态块和实例字段,并在返回时将它们置于构造函数之前.


好的,现在是问题.

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

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

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

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

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

  4. 我的结论是否有误?

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

解决方案

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

类初始化

一个类或接口在首先访问,通过分配编译时常量字段,然后递归初始化超类(如果尚未初始化),然后处理静态初始化程序(其中包括用于非编译时静态字段的初始化程序常数).

如您所见,类的初始化本身不会触发其实现的接口的初始化.接口因此在第一次访问时被初始化,通常是通过读取一个不是编译时常量的字段.这种访问可能发生在对初始化程序的评估期间,从而导致递归初始化.

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

<块引用>

对作为常量变量的字段的引用(第 4.12.4 节)必须在编译时解析为常量变量的初始值设定项所表示的值 V.

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

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

对象初始化

一个对象被初始化每当创建新对象,通常是通过评估类实例创建表达式.其过程如下:

<块引用>

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

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

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

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

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

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

I am trying to piece together the process of Initialization and Instantiation in the JVM but the JLS is a little obtuse on a few details, so if anyone would mind clearing up some details it would be appreciated. This is what I have been able to figure out so far.

Initialization

  1. Recursively Initialize static final variables of the class and it's interfaces that are compile time constants.

  2. Back out of the recursion processing static blocks and static fields in textual order.

Instantiation

  1. Recursively Initialize final instance variables of the class that are compile time constants.

  2. Back out of the recursion processing non-static blocks and instance fields in textual order prepending them to the constructors as it returns.


Okay, so now for the questions.

  1. are interfaces processed in order of declaration?

  2. are interfaces processed in a separate recursive stack?

    a) if yes, do interfaces get processed before or after superclasses?

    b) if yes, am I correct in deducing that one or the others (Interface or Superclass) gets its non-compile-time constant fields initialized before the others compile-time constants.

  3. What role does calls to the nondefault super() constructor play in this process?

  4. Am I mistaken in any of my conclusions?

  5. Am I missing any other key details?

解决方案

It is important to distinguish between the initialization of a class, and initialization of an object.

Class Initialization

A class or interface is initialized upon first access, by assigning the compile time constant fields, then recursively initializing the superclass (if not already initialized), then processing the static initializers (which include the initializers for for the static fields that are not compile time constants).

As you have noticed, initialization of a class does not, by itself, trigger initialization of the interfaces it implements. Interfaces are therefore initialized when they are first accessed, typically by reading a field that is not a compile time constant. This access may occur during evaluation of an initializer, causing a recursive initialization.

It is also worth noting that initialization is not triggered by accessing fields that are compile time constants, as these are evaluated at compile time:

A reference to a field that is a constant variable (§4.12.4) must be resolved at compile time to the value V denoted by the constant variable's initializer.

If such a field is static, then no reference to the field should be present in the code in a binary file, including the class or interface which declared the field. Such a field must always appear to have been initialized (§12.4.2); the default initial value for the field (if different than V) must never be observed.

If such a field is non-static, then no reference to the field should be present in the code in a binary file, except in the class containing the field. (It will be a class rather than an interface, since an interface has only static fields.) The class should have code to set the field's value to V during instance creation (§12.5).

Object Initialization

An object is initialized whenever a new object is created, typically by evaluation of a class instance creation expression. This proceeds as follows:

  1. Assign the arguments for the constructor to newly created parameter variables for this constructor invocation.

  2. If this constructor begins with an explicit constructor invocation (§8.8.7.1) of another constructor in the same class (using this), then evaluate the arguments and process that constructor invocation recursively using these same five steps. If that constructor invocation completes abruptly, then this procedure completes abruptly for the same reason; otherwise, continue with step 5.

  3. This constructor does not begin with an explicit constructor invocation of another constructor in the same class (using this). If this constructor is for a class other than Object, then this constructor will begin with an explicit or implicit invocation of a superclass constructor (using super). Evaluate the arguments and process that superclass constructor invocation recursively using these same five steps. If that constructor invocation completes abruptly, then this procedure completes abruptly for the same reason. Otherwise, continue with step 4.

  4. Execute the instance initializers and instance variable initializers for this class, assigning the values of instance variable initializers to the corresponding instance variables, in the left-to-right order in which they appear textually in the source code for the class. If execution of any of these initializers results in an exception, then no further initializers are processed and this procedure completes abruptly with that same exception. Otherwise, continue with step 5.

  5. Execute the rest of the body of this constructor. If that execution completes abruptly, then this procedure completes abruptly for the same reason. Otherwise, this procedure completes normally.

As we can see in step 3, the presence of an explicit call to the super constructor simply changes which super class constructor is invoked.

这篇关于Java 初始化和实例化顺序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆