西北偏北

North by NorthWest

  DonewsBlog  |  Donews首页  |  Donews社区  |  Donews邮箱  |  我的首页  |  联系作者  |  聚合   |  登录
  38篇文章 :: 0篇收藏:: 13篇评论:: 0个Trackbacks

公告

Welcome to you!
首先,我要衷心地感谢您的光临。
本Blog所载资料很多是从网上所得,如果冒犯了您,请同我联系,我将及时做出调整。
如果所载资料您感兴趣,也请您费心多鼓励鼓励!

文章

收藏

相册

常用站点

我在网络

存档


正在读取评论……


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

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

一般來說,越高階的程式語言,會提供越多語法上的便利,以方便程式撰寫,這就俗稱為 syntax 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:

// 程式 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
  // 所以此時 counter1counter2 都已經是 0,且 singletonnull
  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 初始化時(這時候,counter1counter2 都是 0),就呼叫 instance constructor,而 instance constructor 竟然還會去更動 static field 的值,使得 counter1counter2 都變成 1。然後 instance constructor 執行完,回到 class constructor,再把 counter2 的值設為 0(但是 counter1 維持不變)。最後的結果:counter1 等於 1counter2 等於 0

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

  • 方法一:將 singleton field 的宣告調到 counter1counter2 field 之後。這是最好的作法。
  • 方法二:將 counter2=0 的宣告中,「=0」的部分刪除。這種作法只有在希望 counter2 的初始值是 0 時才有效。
  • 方法三:將初始化的動作搬到 class constructors 內,自行撰寫,而不依賴編譯器產生。這是最保險的作法。

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

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

下面是我用 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 的 syntax sugar 並不算多,C# 的 syntax sugar 才真的是無所不在,也因此 C# 的初學者更容易犯了「全世界所有程式員都會犯的錯誤」。許多 C# 的書都會一邊介紹 C# 語法,一邊介紹編譯之後 MSIL(.NET 的中間語言,類似 Java 的 Bytecode)的結果,然而 Java 的書卻鮮少這麼做。

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

 

本文作者:蔡學鏞
文章出處:Sleepless 2.0
張貼日期:03/10/2003



Trackback: http://tb.donews.net/TrackBack.aspx?PostId=104636


[点击此处收藏本文]  发表于2004年09月20日 8:54 PM




正在读取评论……

发表评论

大名:
网址:
验证码
评论