有關(guān)Java虛擬機類加載機制相關(guān)的文章一搜一大把丐谋,筆者這里也不必再贅述一遍了芍碧。筆者這里撈出一道code題要各位大佬來把玩把玩,如果你一眼就看出了端倪号俐,那么恭喜你泌豆,你可以下山了:
public class StaticTest
{
public static void main(String[] args)
{
staticFunction();
}
static StaticTest st = new StaticTest();
static
{
System.out.println("1");
}
{
System.out.println("2");
}
StaticTest()
{
System.out.println("3");
System.out.println("a="+a+",b="+b);
}
public static void staticFunction(){
System.out.println("4");
}
int a=110;
static int b =112;
}
問題:請問這段程序的輸出是什么?一般對于這類問題吏饿,小伙伴們腦海中肯定浮現(xiàn)出這樣的知識點:
Java中賦值順序:
父類的靜態(tài)變量賦值
自身的靜態(tài)變量賦值
父類成員變量賦值和父類塊賦值
父類構(gòu)造函數(shù)賦值
自身成員變量賦值和自身塊賦值
自身構(gòu)造函數(shù)賦值
按照這個理論輸出是什么呢踪危?答案輸出:1 4,這樣正確囍砺洹贞远?肯定不正確啦,這里不是說上面的規(guī)則不正確笨忌,而是說不能簡單的套用這個規(guī)則蓝仲。 正確的答案是:
2
3
a=110,b=0
1
4
有沒有答對呢?這里主要的點之一:實例初始化不一定要在類初始化結(jié)束之后才開始初始化。 類的生命周期是:加載->驗證->準(zhǔn)備->解析->初始化->使用->卸載袱结,只有在準(zhǔn)備階段和初始化階段才會涉及類變量的初始化和賦值亮隙,因此只針對這兩個階段進行分析;
類的準(zhǔn)備階段需要做是為類變量分配內(nèi)存并設(shè)置默認值擎勘,因此類變量st為null咱揍、b為0;(需要注意的是如果類變量是final棚饵,編譯時javac將會為value生成ConstantValue屬性煤裙,在準(zhǔn)備階段虛擬機就會根據(jù)ConstantValue的設(shè)置將變量設(shè)置為指定的值,如果這里這么定義:static final int b=112,那么在準(zhǔn)備階段b的值就是112噪漾,而不再是0了硼砰。)
類的初始化階段需要做的是執(zhí)行類構(gòu)造器(類構(gòu)造器是編譯器收集所有靜態(tài)語句塊和類變量的賦值語句按語句在源碼中的順序合并生成類構(gòu)造器,對象的構(gòu)造方法是()欣硼,類的構(gòu)造方法是()题翰,可以在堆棧信息中看到),因此先執(zhí)行第一條靜態(tài)變量的賦值語句即st = new StaticTest ()诈胜,此時會進行對象的初始化豹障,對象的初始化是先初始化成員變量再執(zhí)行構(gòu)造方法,因此打印2->設(shè)置a為110->執(zhí)行構(gòu)造方法(打印3,此時a已經(jīng)賦值為110焦匈,但是b只是設(shè)置了默認值0血公,并未完成賦值動作),等對象的初始化完成后繼續(xù)執(zhí)行之前的類構(gòu)造器的語句缓熟,接下來就不詳細說了累魔,按照語句在源碼中的順序執(zhí)行即可。
這里面還牽涉到一個冷知識够滑,就是在嵌套初始化時有一個特別的邏輯垦写。特別是內(nèi)嵌的這個變量恰好是個靜態(tài)成員,而且是本類的實例彰触。 這會導(dǎo)致一個有趣的現(xiàn)象:“實例初始化竟然出現(xiàn)在靜態(tài)初始化之前”梯投。 其實并沒有提前,你要知道java記錄初始化與否的時機况毅⊥砘铮看一個簡化的代碼,把關(guān)鍵問題解釋清楚:
public class Test {
public static void main(String[] args) {
func();
}
static Test st = new Test();
static void func(){}
}
根據(jù)上面的代碼俭茧,有以下步驟:
1.首先在執(zhí)行此段代碼時咆疗,首先由main方法的調(diào)用觸發(fā)靜態(tài)初始化。
2.在初始化Test 類的靜態(tài)部分時母债,遇到st這個成員午磁。
3.但湊巧這個變量引用的是本類的實例尝抖。
4.那么問題來了,此時靜態(tài)初始化過程還沒完成就要初始化實例部分了迅皇。是這樣么昧辽?
5.從人的角度是的。但從java的角度登颓,一旦開始初始化靜態(tài)部分搅荞,無論是否完成,后續(xù)都不會再重新觸發(fā)靜態(tài)初始化流程了框咙。
6.因此在實例化st變量時咕痛,實際上是把實例初始化嵌入到了靜態(tài)初始化流程中,并且在樓主的問題中喇嘱,嵌入到了靜態(tài)初始化的起始位置茉贡。這就導(dǎo)致了實例初始化完全至于靜態(tài)初始化之前。這也是導(dǎo)致a有值b沒值的原因者铜。
7.最后再考慮到文本順序腔丧,結(jié)果就顯而易見了。
詳細看到這里作烟,心中大概有個結(jié)論了吧愉粤。