我們先來看一個例子,如果你讀過《java程式設計思想》的話 應該會有印象
1 package com.test.zj; 2 3 public class PolyConstructors { 4 5 public static void main(String[] args) { 6 // TODO Auto-generated method stub 7 new RoundGlyph(5); 8 } 9 10 } 11 12 class RoundGlyph extends Glyph { 13 private int radius = 1; 14 15 public RoundGlyph(int r) { 16 // TODO Auto-generated constructor stub 17 radius = r; 18 System.out.println("RoundGlyph radius==" + radius); 19 } 20 21 @Override 22 void draw() { 23 // TODO Auto-generated method stub 24 System.out.println("RoundGlyph draw() radius==" + radius); 25 } 26 27 } 28 29 class Glyph { 30 void draw() { 31 System.out.println("print glyph.draw()"); 32 } 33 34 Glyph() { 35 System.out.println("Glyph() before draw()"); 36 draw(); 37 System.out.println("Glyph() after draw()"); 38 39 } 40 41 }
對於java基礎一般的同學來說 這裡你可能會認為輸出是如下:
1 Glyph() before draw() 2 RoundGlyph draw() radius==1 3 Glyph() after draw() 4 RoundGlyph radius==5
但實際上你執行完畢以後 你會發現他的輸出是這樣的:
可能有的人讀到這裡還是不太明白我要表述什麼,那我再寫一個簡單的例子。先定義一個父類SuperClass
1 package com.test.zj; 2 3 public class SuperClass 4 { 5 private int superValue; 6 7 public SuperClass() 8 { 9 setSuperValue(100); 10 11 } 12 13 public void setSuperValue(int x) 14 { 15 superValue=x; 16 } 17 18 }
然後我們定義它的子類:
1 //這個子類繼承自父類superclass 2 public class SubClass extends SuperClass 3 { 4 private int subValue=10; 5 6 public SubClass() 7 { 8 9 } 10 //這個方法重寫了父類的方法 11 public void setSuperValue(int x) 12 { 13 //先呼叫父類的方法 14 super.setSuperValue(x); 15 //然後把值賦給自己的變數 16 subValue=x; 17 18 } 19 20 public void printSubValue() 21 { 22 System.out.println("subclass subvalue=="+subValue); 23 } 24 25 }
最後寫個main函式 就可以了
1 package com.test.zj; 2 3 public class MainClass { 4 5 public static void main(String[] args) { 6 // TODO Auto-generated method stub 7 SubClass sc=new SubClass(); 8 sc.printSubValue(); 9 } 10 11 }
好,現在我相信很多人都會認為第二個例子輸出的結果應該是100
但其實並沒有什麼卵用,他的實際結果是:
那到底這兩個例子都發生了什麼呢,我們直接來看位元組碼好了,這個位元組碼肯定不會有錯,位元組碼怎麼寫的 jvm就怎麼執行。
我們就先看看第一個例子。
這裡應該很明顯的能看到 我們的main函式 一開始就是new了RoundGlyph這個物件。那我們看看這個類-c的結果吧
可以看到這個類的建構函式
先執行的是這個:
也就是說 先執行了glyph的構造方法 然後當glyph的建構函式執行完畢以後 才執行的賦值語句
我們的radius 作為一個int變數 在被執行之前 jvm自動初始化他的值為0!
所以你這裡隱隱約約應該都能猜到一個大概了,先執行的glyph的 建構函式,然後再給自己的成員變數radius賦值。
那我們看看glyph 都做了什麼吧:
你看glyph的建構函式, 在中間的時候13:invokevirtual #31 這裡,去執行了draw方法,但是子類我們重寫了這個draw方法
所以你看 在glyph的建構函式裡 呼叫子類的draw方法的時候 子類的radius賦值語句並沒有被執行到,所以子類的這個方法
輸出的值當然是0!
當父類glyph的建構函式執行完畢以後 ,我們的子類的賦值語句才終於得到執行。所以到這裡 你應該能明白第一個例子了。
那我們現在就可以去研究一下第二個例子,其實都是大同小異的。我們還是先看第二個例子的manclass和main函式
你看這裡main函式 先是new了一個subclass 子類的物件 對吧。那我們當然就要去看看subclass init方法
實際上這個地方就是Subclass的建構函式了。
這裡很清楚的可以看到 在subclass的建構函式裡 我們是先執行的superclass的建構函式,然後才給自己的subValue賦值為10.
那我們就去看看superclass裡都做了什麼。但實際上走到這裡我們已經能想到了無論你在superclass做了什麼 當你做完以後
subValue的值都必定為10.
所以當你subclass的物件構造完畢以後 此時他的成員變數subvalue的值就是10了,所以你當然列印出來這個變數的值 就一定是10了。
當然為了更清晰一點 我還是把superclass建構函式裡做了什麼稍微講一下,雖然這裡面做了什麼不會影響到我們的結論,但還是講一下吧,
即使這並沒有什麼卵用。。。
你看這裡就是呼叫了一下setSuperValue這個方法麼,對吧,因為子類重寫了這個方法 所以我們肯定要看看子類
這個方法幹嘛的:
你看不就是又呼叫了父類的setSupervalue方法嗎,然後呼叫以後 你看有個iload putfield
這2個操作不就是給我們子類的subvalue 賦值的嗎,對吧。一直到這裡,我們子類的物件建構函式的第一步:
呼叫父類的建構函式 就算是走完了,走完了以後 才終於執行了自己的賦值語句:
好,這2個例子到這裡就算分析完畢了。
實際上最終的結論就是java程式設計思想裡說的那樣:
父類static成員 -> 子類static成員 -> 父類普通成員初始化和初始化塊 -> 父類構造方法 -> 子類普通成員初始化和初始化塊 -> 子類構造方法
如果你們有興趣的話,可以寫一個稍微更復雜一點的程式,驗證一下 上面的這個結論是否成立,廢話。。。。這結論肯定是成立的。但是
你如果用javap -c 這個命令 去看他們的位元組碼的話 相信你能理解的更深了!
最後多說一句,平常我們在寫程式碼的時候,儘量避免 上述2個例子這樣的寫法,因為這種情況造成的bug 很難被發現。。。即:
儘量不要在父類的建構函式裡 操作子類的成員變數。如果一定要把初始化寫的很麻煩的話,請考慮使用初始化塊 這樣一目瞭然的方法!
別問我為什麼會研究到這,因為tmd 有一個bug 找了好久 發現是這個原因啊!所以以後你們發現有人這麼寫,請直接寫郵件抄送全組投訴他啊!