构造/初始化顺序?

huangapple go评论62阅读模式
英文:

Order of construction/initialization?

问题

NB: 我添加了android标签,因为我只在Android自己的虚拟机上尝试过这个,作为一个应用程序的一部分。其他JVM可能会有不同的行为。


这是我代码的简化版本:

public class Test {
    public static class Base {
        public final Object base = new Object();

        public Base() {
            print();
        }

        public void print() {
            System.err.println(String.format("base = %s", base));
        }
    }

    public static class Outer extends Base {
        public final Object member = new Object();

        public Outer() {
            super();
        }

        @Override
        public void print() {
            super.print();
            System.err.println(String.format("member = %s", member));
        }
    }

    public static void main(String[] args) {
        new Outer();
    }
}

输出:

base = java.lang.Object@9a9e0fa
member = null

为什么member是NULL?我本以为任何非静态类字段在构造函数运行之前都会被初始化。

英文:

NB: I added the android tag because I've only tried this on Android's own VM, as part of an app. Other JVMs might or might not behave the same way.


Here's a simplified version of my code:

public class Test {
	public static class Base {
		public final Object base = new Object();

		public Base() {
			print();
		}

		public void print() {
			System.err.println(String.format("base = %s", base));
		}
	}

	public static class Outer extends Base {
		public final Object member = new Object();

		public Outer() {
			super();
		}

		@Override
		public void print() {
			super.print();
			System.err.println(String.format("member = %s", member));
		}
	}

	public static void main(String[] args) {
		new Outer();
	}
}

Output:

base = java.lang.Object@9a9e0fa
member = null

Why is member NULL? I was under the impression any nonstatic class fields are initialized before the constructor runs.

答案1

得分: 3

> 我的印象是,任何非静态类字段在构造函数运行之前都会被初始化。

你的印象是错误的。

实际发生的是,对象的所有超类初始化都会在对象的任何子类初始化之前发生。

<sup>上述经验法则仅适用于对象实例初始化。类(即static)初始化要复杂一些。但是这个问题是关于实例初始化的。</sup>

该过程在JLS 12.5中详细描述。相关部分如下:

> 在新创建的对象被返回为结果之前,将处理所示构造函数,以使用以下过程初始化新对象:
>
> 1. 将构造函数的参数分配给该构造函数调用的新创建参数变量。
>
> 2. 如果此构造函数以同一类中另一个构造函数的显式构造函数调用(§8.8.7.1)开始(使用this),则评估参数并使用相同的五个步骤递归地处理该构造函数调用。如果该构造函数调用中止,则出于相同的原因,此过程中止;否则,继续执行第5步。
>
> 3. 此构造函数不以另一构造函数的显式构造函数调用(使用this)开始。如果此构造函数不是用于Object类,则此构造函数将以超类构造函数的显式或隐式调用开始(使用super)。**评估参数并使用相同的五个步骤递归地处理该超类构造函数调用。**如果该构造函数调用中止,则出于相同的原因,此过程中止。否则,继续执行第4步。
>
> 4. 执行此类的实例初始化程序和实例变量初始化程序,将实例变量初始化程序的值分配给源代码中按从左到右的文本顺序出现的相应实例变量。如果执行这些初始化程序中的任何一个导致异常,则不会进一步处理任何初始化程序,并且此过程将以相同的异常突然中止。否则,继续执行第5步。
>
> 5. 执行此构造函数的其余部分。如果该执行中止,则出于相同的原因,此过程中止。否则,此过程正常完成。

请注意我所突出的那句话。将其放在其余部分的上下文中,这意味着 Base() 的构造函数体在 Outer 的实例字段初始化之前运行。


> 其他的 JVM 可能会表现得不同。

实际上,所有的 JVM 应该都会以这种方式运行。这是 Java 语言规范规定的行为。

英文:

> I was under the impression any nonstatic class fields are initialized before the constructor runs.

Your impression is / was incorrect.

What actually happens is that all superclass initialization for an object happens before any subclass initialization for the object.

<sup>The above rule-of-thumb is for object instance initialization only. Class (i.e. static) initialization is a bit more complicated. But this question is about instance initialization.</sup>

The process is described in detail in JLS 12.5. The relevant part is this:

> Just before a reference to the newly created object is returned as the
> result, the indicated constructor is processed to initialize the new
> object using the following procedure:
>
> 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.

Note the sentence that I have highlight. When you take that in the context of the rest, that means that the constructor body for Base() runs before the initialization of the instance fields for Outer.


> Other JVMs might or might not behave the same way.

In fact all JVMs should behave this way. This is how the Java language is specified to behave.

huangapple
  • 本文由 发表于 2020年4月10日 13:55:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/61134777.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定