今天我在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类对象初始化顺序定义,这是一个带条件分支的流程描述:
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
). 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. 引用自 section 12.5 of the Java Language Spec