Java 初始化和实例化顺序
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/23093470/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Java order of Initialization and Instantiation
提问by user3056052
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.
我试图将 JVM 中的初始化和实例化过程拼凑起来,但 JLS 在一些细节上有点迟钝,所以如果有人介意澄清一些细节,我们将不胜感激。这是我迄今为止能够弄清楚的。
Initialization
初始化
Recursively Initialize static final variables of the class and it's interfaces that are compile time constants.
Back out of the recursion processing static blocks and static fields in textual order.
递归初始化类的静态最终变量及其作为编译时常量的接口。
按文本顺序退出递归处理静态块和静态字段。
Instantiation
实例化
Recursively Initialize final instance variables of the class that are compile time constants.
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.
好的,现在来回答问题。
are interfaces processed in order of declaration?
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.
What role does calls to the nondefault super() constructor play in this process?
Am I mistaken in any of my conclusions?
Am I missing any other key details?
接口是否按声明的顺序处理?
接口是否在单独的递归堆栈中处理?
a) 如果是,接口是在超类之前还是之后处理?
b)如果是,我是否正确推断出一个或其他(接口或超类)在其他编译时常量之前初始化其非编译时常量字段。
对非默认 super() 构造函数的调用在此过程中扮演什么角色?
我的任何结论是否有误?
我是否遗漏了任何其他关键细节?
采纳答案by meriton
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).
对作为常量变量(第 4.12.4 节)的字段的引用必须在编译时解析为常量变量的初始值设定项所表示的值 V。
如果这样的字段是静态的,则二进制文件的代码中不应存在对该字段的引用,包括声明该字段的类或接口。这样的字段必须始终显示已初始化(第 12.4.2 节);不得观察字段的默认初始值(如果不同于 V)。
如果这样的字段是非静态的,那么在二进制文件的代码中不应该存在对该字段的引用,除了在包含该字段的类中。(它将是一个类而不是接口,因为接口只有静态字段。)该类应该有代码在实例创建期间将字段的值设置为 V(第 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:
每当创建新对象时都会初始化对象,通常是通过评估类实例创建表达式。其过程如下:
Assign the arguments for the constructor to newly created parameter variables for this constructor invocation.
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.
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.
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.
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.
将构造函数的参数分配给此构造函数调用新创建的参数变量。
如果此构造函数以同一类中另一个构造函数的显式构造函数调用(第 8.8.7.1 节)开始(使用 this),则评估参数并使用相同的五个步骤递归处理该构造函数调用。如果构造函数调用突然完成,那么这个过程也会因为同样的原因突然完成;否则,继续第 5 步。
此构造函数不以对同一类中另一个构造函数的显式构造函数调用开始(使用 this)。如果此构造函数用于 Object 以外的类,则此构造函数将以显式或隐式调用超类构造函数(使用 super)开始。使用这五个相同的步骤,递归地评估超类构造函数调用的参数和过程。如果该构造函数调用突然完成,则此过程出于同样的原因而突然完成。否则,继续第 4 步。
执行该类的实例初始值设定项和实例变量初始值设定项,将实例变量初始值设定项的值分配给相应的实例变量,按照它们在类的源代码中以文本形式出现的从左到右的顺序。如果执行这些初始化程序中的任何一个导致异常,则不会处理进一步的初始化程序,并且此过程会突然完成并出现相同的异常。否则,继续第 5 步。
执行此构造函数的其余部分。如果该执行突然完成,那么这个过程也会出于同样的原因突然完成。否则,此过程将正常完成。
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.
正如我们在第 3 步中看到的,对超构造函数的显式调用的存在只是改变了调用哪个超类构造函数。
回答by Eric Wang
Following is an example that print the order of each step during object creation.
以下是在对象创建期间打印每个步骤的顺序的示例。
InstanceCreateStepTest.java:
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");
}
}
Execution:
执行:
Just invoke the main method, and then check the output.
只需调用 main 方法,然后检查输出。
Tips:
提示:
- The methods marked by
@PostConstruct
won't be invoked, unless you invoke it inside some container, likeSpring-boot
, since it depends on those containers to implement annotation like@PostConstruct
.
- 标记为 的方法
@PostConstruct
不会被调用,除非您在某个容器中调用它,例如Spring-boot
,因为它依赖于这些容器来实现注释,例如@PostConstruct
。