如果你忽略Java的細節,恐怕你的代碼會充滿bug,下面討論關於類成員初始化問題。
第一類,初始化成員變數在構造方法之前
主要參考TIJ中的代碼,來說明問題!!
1. 建立一個類Tag
package mark.initial;<br />public class Tag {</p><p>/**<br /> * 構造方法<br /> *<br /> * @param maker<br /> */<br />public Tag(int maker) {<br />System.out.println("tag(" + maker + ")");<br />}<br />}<br />
2. 建立一個類Card
package mark.initial;<br />public class Card {</p><p>Tag tag1 = new Tag(1);</p><p>/**<br /> * 構造方法<br /> */<br />public Card() {<br />System.out.println("card()");<br />Tag tag3 = new Tag(33);<br />}</p><p>Tag tag2 = new Tag(2);</p><p>/**<br /> * 成員方法<br /> */<br />public void f() {<br />System.out.println("f()");<br />}</p><p>Tag tag3 = new Tag(3);<br />}<br />
3. 建立測試類別
package mark.initial;<br />public class TestInitial {</p><p>public static void main(String[] args) {<br />Card c = new Card();<br />c.f();<br />}<br />}<br />
看結果之前,簡單分析一下。在Card中,到處都有Tag對象,看起來比較亂。其實這是故意的,我們知道類的成員變數會被初始化為預設值比如引用初始化為null,int預設為0,float預設為0.0等,如果你沒有指定這些成員變數的值時。
在測試類別TestInitial中,new一個Card,這樣就會初始化它的成員變數tag1、tag2、tag3先為null,(由於我們手動將這些成員變數賦予新值即new該對象),然後會指向堆裡面的對象。最後Card調用自己的建構函式,所以結果如下所示:
tag(1)<br />tag(2)<br />tag(3)<br />card()<br />tag(33)<br />f()
第二類,成員變數初始化在構造方法之後
子類成員變數初始化,父類Linux,子類User重寫父類的print()方法,並在該方法中改變成員變數name值。
package my.test;public class Linux {int size = 10;String name = "Linux";/** * 構造方法 */public Linux() {System.out.println("I'm Linux OS!");print();}public void print() {System.out.println("父類Linux--print()");}}class User extends Linux {String name = "ubuntu";@Overridepublic void print() {name = "ubuntu10.10";System.out.println("子類User--print()");}}
測試代碼:
class TestLinux {</p><p>public static void main(String[] args) {<br />User user = new User();<br />System.out.println(user.name);<br />}<br />}
在main方法中建立User執行個體對象user,User會調用自己的構造方法,然而我們知道子類在其構造方法中會先調用父類的構造方法,這樣一來User的父類Linux會執行自己的構造方法,從而調用print()方法,由於子類User覆寫父類方法,所以調用的是子類的print()方法。將name的值變為ubuntu10.10,構造方法執行完畢,開始初始化User的成員變數即name為ubuntu,那麼列印結果就是如下所示:
I'm Linux OS!子類User--print()ubuntu
第三類,其實這是第二類的另一個執行個體
下面是單例模式:
package my.test;<br />public class ClassloadTest {<br />// 聲明類成員變數並建立該對象<br />static final ClassloadTest test = new ClassloadTest();</p><p>public static int a = 5;<br />public static int b = 8;</p><p>/**<br /> * 私人構造方法<br /> */<br />private ClassloadTest() {<br />a++;<br />b++;<br />}</p><p>/**<br /> * 擷取類的執行個體對象<br /> *<br /> * @return ClassloadTest執行個體對象<br /> */<br />public static ClassloadTest getInstance() {<br />return test;<br />}<br />}
好了,看懂上述代碼之後,看看測試代碼:
class Test {<br />public static void main(String[] args) {<br />System.out.println("a = " + ClassloadTest.getInstance().a);<br />System.out.println("b = " + ClassloadTest.getInstance().b);<br />}<br />}
在公布答案之前,大部分人都會很自信的說結果是下面的樣子:
a = 6<br />b = 9
呵呵,你太衝動啦!!!回答錯誤,好好想一想吧!!
下面這句代碼是建立ClassloadTest 對象 test:
// 聲明類成員變數並建立該對象<br />static final ClassloadTest test = new ClassloadTest();
在測試類別中,獲得類的執行個體即建立對象,就會調用構造方法,其實發生的時間是這樣子的:
<1> 初始化test為null,a=0,b=0,這裡的值是預設賦值,不是你手動所賦的值5和8
<2> test = new ClassloadTest(),會調用構造方法,從而使a=1,b=1
<3> 順序執行代碼,靜態變數a=5,b=8,那麼原來的a=1,b=1就被覆蓋掉
ok,執行結果應該是:
a = 5<br />b = 8
如果,改變代碼中static final ClassloadTest test = new ClassloadTest();的位置結果會不一樣的。現在改變ClassloadTest類如下:
package my.test;<br />public class ClassloadTest {</p><p>public static int a = 5;<br />public static int b = 8;</p><p>// 聲明類成員變數並建立該對象<br />static final ClassloadTest test = new ClassloadTest();</p><p>/**<br /> * 私人構造方法<br /> */<br />private ClassloadTest() {<br />a++;<br />b++;<br />}</p><p>/**<br /> * 擷取類的執行個體對象<br /> *<br /> * @return ClassloadTest執行個體對象<br /> */<br />public static ClassloadTest getInstance() {<br />return test;<br />}<br />}
還是上使用上面的測試方法,結果如下:
a = 6<br />b = 9<br />
分析如下:
<1> 初始化test為null,a=0,b=0,這裡的值是預設賦值,不是你手動所賦的值5和8
<2> a=5,b=8
<3> test = new ClassloadTest(),會調用構造方法,從而使a=6,b=9
小結:
說到這裡,我們至少明白下面的道理(針對類成員變數):
<1> 物件導向編程,也需要考慮聲明變數的順序
<2> Java中的聲明和初始化不是原子操作,即他們不是一體化的,也就是說聲明後它會有一個預設值,初始化值是可以手動賦值的。