論全世界所有程序員都會犯的錯誤 – JAVA編程語言程序開發技術文章

當年,某國際巨星的“龍種”曝光,眾人指責他對不起嬌妻,逼得他出面召開記者會,向世人自白他犯瞭“全世界所有男人都會犯的錯誤”。從來沒犯過這種錯誤的我,也因此常常認為自己不是個男人。

雖然沒犯過“全世界所有男人都會犯的錯誤”,但是我倒是曾經犯瞭“全世界所有程序員都會犯的錯誤”。不管使用何種語言,全世界所有程序員都一定犯過這種錯誤,那就是:太依賴編譯器,卻不知道編譯器做瞭哪些事。

一般來說,越高階的程序語言,會提供越多語法上的便利,以方便程序撰寫,這就俗稱為syntactic sugar,我稱其為“語法上的甜頭”。雖說是甜頭,但是如果你未能瞭解該語法的實質內涵,很可能會未嘗甜頭,卻吃盡苦頭。

不久前,我收到一個電子郵件,讀者列出下面的Java程序,向我求救。看過這個程序之後,我確定這又是一個“全世界所有程序員都會犯的錯誤”。

程序1

class Singleton

{

private static Singleton

obj = new Singleton();

public static int counter1;

public static int counter2 = 0;

private Singleton() {

counter1++;

counter2++;

}

public static Singleton getInstance()

{

return obj;

}

}
程序2

public class MyMain {

public static void main(String[] args) {

Singleton obj = Singleton.getInstance();

System.out.println(“obj.counter1==”+obj.counter1);

System.out.println(“obj.counter2==”+obj.counter2);

}

}

執行結果是:

obj.counter1==1

obj.counter2==0

你有沒有被此結果嚇一跳?乍看程序代碼,你很可能會認為counter1和counter2的值一定 會相等,但執行結果顯然不是如此。其實,程序1被編譯後的程序應該等同於下面的程序3 :

class Singleton

{

private static Singleton obj;

public static int counter1;

public static int counter2;

static

{

// 這就是class constructor

// 在進入此class constructor之前,class已經被JVM

// 配置好內存,所有的static field都會被先設定為0,

// 所以此時counter1和counter2都已經是0,

且singleton為null

obj = new Singleton();

// 問題皆由此行程序產生

// counter1不會在此被設定為0

counter2 = 0;

// counter2再被設定一次0(其實是多此一舉)

}

private Singleton()

{

// 這是instance constructor

counter1++;

counter2++;

}

public static Singleton getInstance()

{

return obj;

}
這是因為:當class具有static field,且直接在宣告處透過“=…”的方式設定其值時,編譯器會自動將這些敘述依序搬到class constructor內。同樣地,當class具有instance field,且直接在宣告處透過“=…”的方式設定其值時,編譯器會自動將這些敘述依序搬到instance constructor內。

此程序在class constructor內,還未將static field初始化時(這時候,counter1和counter2都是0),就呼叫instance constructor,而instance constructor竟然還會去更動static field的值,使得counter1和counter2都變成1。

然後instance constructor執行完,回到class constructor,再把counter2的值設為0(但是counter1維持不變)。最後的結果:counter1等於1,counter2等於0。

欲改正程序1,方法有三:

方法一:將singleton field的宣告調到counter1與counter2 field之後。

這是最好的作法。

方法二:將counter2=0的宣告中,“=0”的部分刪除。這種作法隻有在希望。

方法三:將初始化的動作搬到class constructors內,自行撰寫,而不依賴編譯器產生。這是最保險的作法。

如何避免犯下“全世界所有程序員都會犯的錯誤”,我給各位Java程序員的建議是:

-熟讀Java Language Specification

-在有疑問時,使用J2SDK所提供的javap來反組譯Java Bytecode,直接觀察編譯後的結果。

發佈留言