今天我在Dzone閱讀了一篇關于java對象實例初始化順序的有趣文章新锈。說它有趣妄讯,是因為作者使用了一種并不太推薦的編碼風格,只有用這種編碼風格才能觸發(fā)這個極為少見的 Java object initialization order 問題凭疮。
其實java對象初始化順序算是一個比較基礎的java知識點西壮。但是網(wǎng)上的文章多半描述不清,使用上一不小心就容易出問題张咳。
所以在本文中帝洪,我想結(jié)合JLS和自己的理解,舉例剖析問題的所在晶伦。
OK碟狞,我們先來看個模仿Dzone作者原意的簡單例子:
[java]
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);
}
}
[/java]
例子代碼很簡單啄枕,不多做解釋了婚陪,直接看輸出:
0
100
200
對照這個輸出,我們來詳細分析一下對象的初始化順序:
1频祝,為A類分配內(nèi)存空間泌参,初始化所有成員變量為默認值,包括primitive類型(int=0,boolean=false,…)和Reference類型常空。
2沽一,調(diào)用A類構(gòu)造函數(shù)。
3漓糙,調(diào)用B類構(gòu)造函數(shù)铣缠。
4,調(diào)用Object空構(gòu)造函數(shù)昆禽。(java編譯器會默認加此構(gòu)造函數(shù)蝗蛙,且object構(gòu)造函數(shù)是個空函數(shù),所以立即返回)
5醉鳖,初始化B類成員變量捡硅,因為B類沒有成員變量,跳過盗棵。
6壮韭,執(zhí)行sysout輸出子類A的成員變量小a。// 此時為0
7纹因,初始化A類成員變量喷屋,將A類成員變量小a賦值100。
8瞭恰,執(zhí)行sysout輸出當前A類的成員變量小a逼蒙。// 此時為100
9,賦值當前A類的成員變量小a為200。
10是牢,main函數(shù)中執(zhí)行sysout僵井,輸出A類實例的成員變量小a。// 此時為200
加粗的那兩行描述是重點驳棱,結(jié)論是成員變量初始化是在父類構(gòu)造函數(shù)調(diào)用完后批什,在此之前,成員變量的值均是默認值社搅。Dzone作者就是栽在這里驻债,沒有仔細分析成員變量初始化在對象初始化中的順序,造成了程序未按原意執(zhí)行形葬。
其實這類問題合呐,熟悉原理是一方面,本質(zhì)上只要不在構(gòu)造函數(shù)中插入過多的業(yè)務邏輯笙以,出問題的概率也會低很多淌实。
最后,我們再來看看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 (usingthis), 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 (usingthis). If this constructor is for a class other thanObject, then this constructor will begin with an explicit or implicit invocation of a superclass constructor (usingsuper). 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.