weidagang2046的專欄

          物格而后知致
          隨筆 - 8, 文章 - 409, 評論 - 101, 引用 - 0
          數(shù)據(jù)加載中……

          全世界所有程序員都會犯的錯誤-蔡學鏞

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

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

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

          不久前,我收到一個電子郵件,讀者列出下面的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);
          }
          }

          執(zhí)行結(jié)果是:
          obj.counter1==1
          obj.counter2==0

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

          // 程序3
          class Singleton {
          private static Singleton obj;
          public static int counter1;
          public static int counter2;
          static { // 這就是class constructor
          // 在進入此class constructor之前,class已經(jīng)被JVM
          // 配置好內(nèi)存,所有的static field都會被先設(shè)定為0,
          // 所以此時counter1和counter2都已經(jīng)是0,且singleton為null
          obj = new Singleton(); // 問題皆由此行程序產(chǎn)生
          // counter1不會在此被設(shè)定為0
          counter2 = 0; // counter2再被設(shè)定一次0(其實是多此一舉)
          }
          private Singleton() { // 這是instance constructor
          counter1++;
          counter2++;
          }
          public static Singleton getInstance() {
          return obj;
          }
          }

          這是因為:當class具有static field,且直接在宣告處透過「=...」的方式設(shè)定其值時,編譯器會自動將這些敘述依序搬到class constructor內(nèi)。同樣地,當class具有instance field,且直接在宣告處透過「=...」的方式設(shè)定其值時,編譯器會自動將這些敘述依序搬到instance constructor內(nèi)。

          此程序在class constructor內(nèi),還未將static field初始化時(這時候,counter1和counter2都是0),就呼叫instance constructor,而instance constructor竟然還會去更動static field的值,使得counter1和counter2都變成1。然后instance constructor執(zhí)行完,回到class constructor,再把counter2的值設(shè)為0(但是
          counter1維持不變)。最后的結(jié)果:counter1等于1,counter2等于0。

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

          -方法一:將singleton field的宣告調(diào)到counter1與counter2 field之后。
          這是最好的作法。
          -方法二:將counter2=0的宣告中,「=0」的部分刪除。這種作法只有在希望
          -方法三:將初始化的動作搬到class constructors內(nèi),自行撰寫,而不依賴
          編譯器產(chǎn)生。這是最保險的作法。

          如何避免犯下「全世界所有程序員都會犯的錯誤」,我給各位Java程序員
          的建議是:
          -熟讀Java Language Specification
          -在有疑問時,使用J2SDK所提供的javap來反組譯Java Bytecode,直接觀察
          編譯后的結(jié)果。

          下面是我用javap來反組譯程序1的示范:

          C:\>javap -c -classpath . Singleton

          Compiled from MyMain.java
          class Singleton extends java.lang.Object {
          public static int counter1;
          public static int counter2;
          public static Singleton getInstance();
          static {};
          }

          Method Singleton()
          0 aload_0
          1 invokespecial #1 <Method java.lang.Object()>
          4 getstatic #2 <Field int counter1>
          7 iconst_1
          8 iadd
          9 putstatic #2 <Field int counter1>
          12 getstatic #3 <Field int counter2>
          15 iconst_1
          16 iadd
          17 putstatic #3 <Field int counter2>
          20 return

          Method Singleton getInstance()
          0 getstatic #4 <Field Singleton obj>
          3 areturn

          Method static {}
          0 new #5 <Class Singleton>
          3 dup
          4 invokespecial #6 <Method Singleton()>
          7 putstatic #4 <Field Singleton obj>
          10 iconst_0
          11 putstatic #3 <Field int counter2>
          14 return

          其實Java的syntactic sugar并不算多,C#的syntactic sugar才真的是無所不在,
          也因此C#的初學者更容易犯了「全世界所有程序員都會犯的錯誤」。許多C#的書都會一邊介紹C#語法,一邊介紹編譯之后MSIL(.NET的中間語言,類似Java的Bytecode)的結(jié)果,然而Java的書卻鮮少這么做。

          雖說是「全世界所有程序員都會犯的錯誤」,但是這不代表你犯了此錯誤之后,仍可以同愛借錢的曹啟泰一般地「抬頭挺胸、理直氣壯」。只要有心,其實這一類的錯誤仍是可以避免的。


          本文作者:蔡學鏞
          文章出處:Sleepless 2.0
          發(fā)表日期:03/10/2003


          轉(zhuǎn)自:http://www.javaresearch.org/article/showarticle.jsp?column=544&thread=25732

          posted on 2005-04-20 00:21 weidagang2046 閱讀(215) 評論(1)  編輯  收藏 所屬分類: Java

          評論

          # re: 全世界所有程序員都會犯的錯誤-蔡學鏞  回復  更多評論   

          第一次聽說class constructor的概念
          2005-04-20 00:22 | weidagang2046
          主站蜘蛛池模板: 文水县| 招远市| 涞源县| 武义县| 信丰县| 濉溪县| 自贡市| 道真| 湟源县| 太谷县| 绥德县| 九龙城区| 视频| 鄂托克前旗| 弥勒县| 陕西省| 固原市| 富宁县| 鄄城县| 淮安市| 肃南| 保靖县| 永州市| 万全县| 云安县| 农安县| 申扎县| 利辛县| 崇州市| 龙岩市| 龙州县| 平罗县| 莱州市| 英德市| 麻江县| 民乐县| 青龙| 于田县| 德格县| 红河县| 平利县|