今天我在 Dzone 阅读了一篇关于 java 对象实例初始化顺序的有趣文章。说它有趣,是因为作者使用了一种并不太推荐的编码风格,只有用这种编码风格才能触发这个极为少见的 Java object initialization order 问题。
其实 java 对象初始化顺序算是一个比较基础的 java 知识点。但是网上的文章多半描述不清,使用上一不小心就容易出问题。
所以在本文中,我想结合 JLS 和自己的理解,举例剖析问题的所在。
OK,我们先来看个模仿 Dzone 作者原意的简单例子:
- package com.kenwublog.tmp;
- public class A extends B {
- public int a = 100;
- public A() {
- super();
- System.out.println(a);
- a = 200;
- }
- public static void main(String[] args) {
- System.out.println(new A().a);
- }
- }
- class B {
- public B() {
- System.out.println(((A) this).a); //没有错,父类是可以强制转化为子类的 }}
例子代码很简单,不多做解释了,直接看输出:
0
100
200
对照这个输出,我们来详细分析一下对象的初始化顺序:
1,为 A 类分配内存空间,初始化所有成员变量为默认值,包括 primitive 类型 (int=0,boolean=false,…) 和 Reference 类型。
2,调用 A 类构造函数。
3,调用 B 类构造函数。
4,调用 Object 空构造函数。(java 编译器会默认加此构造函数,且 object 构造函数是个空函数,所以立即返回)
5,初始化 B 类成员变量,因为 B 类没有成员变量,跳过。
6,执行 sysout 输出子类 A 的成员变量小 a。// 此时为 0
7,初始化 A 类成员变量,将 A 类成员变量小 a 赋值 100。
8,执行 sysout 输出当前 A 类的成员变量小 a。// 此时为 100
9,赋值当前 A 类的成员变量小 a 为 200。
10,main 函数中执行 sysout,输出 A 类实例的成员变量小 a。// 此时为 200
加粗的那两行描述是重点,结论是成员变量初始化是在父类构造函数调用完后,在此之前,成员变量的值均是默认值。
Dzone 作者就是栽在这里,没有仔细分析成员变量初始化在对象初始化中的顺序,造成了程序未按原意执行。
其实这类问题,熟悉原理是一方面,本质上只要不在构造函数中插入过多的业务逻辑,出问题的概率也会低很多。
最后,我们再来看看 JLS 中给出的 Java 类对象初始化顺序定义,这是一个带条件分支的流程描述:
Assign the arguments for the constructor to newly created parameter variables for this constructor invocation.
If this constructor begins with an explicit constructor invocation 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. (In some early implementations, the compiler incorrectly omitted the code to initialize a field if the field initializer expression was a constant expression whose value was equal to the default initialization value for its type.)
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.
就爱阅读 www.92to.com 网友整理上传, 为您提供最全的知识大全, 期待您的分享,转载请注明出处。
来源: