讀古今文學網 > 編寫高質量代碼:改善Java程序的151個建議 > 建議20:不要只替換一個類 >

建議20:不要只替換一個類

我們經常在系統中定義一個常量接口(或常量類),以囊括系統中所涉及的常量,從而簡化代碼,方便開發,在很多的開源項目中已採用了類似的方法,比如在Struts2中,org.apache.struts2.StrutsConstants就是一個常量類,它定義了Struts框架中與配置有關的常量,而org.apache.struts2.StrutsStatics則是一個常量接口,其中定義了OGNL訪問的關鍵字。

關於常量接口(類)我們來看一個例子,首先定義一個常量類:


public class Constant{

//定義人類壽命極限

public fnal static int MAX_AGE=150;

}


這是一個非常簡單的常量類,定義了人類的最大年齡,我們引用這個常量,代碼如下:


public class Client{

public static void main(Stringargs){

System.out.println("人類壽命極限是:"+Constant.MAX_AGE);

}

}


運行的結果非常簡單(結果省略)。目前的代碼編寫都是在「智能型」IDE工具中完成的,下面我們暫時回溯到原始時代,也就是回歸到用記事本編寫代碼的年代,然後看看會發生什麼奇妙事情(為什麼要如此,稍後會給出答案)。

修改常量Constant類,人類的壽命增加了,最大能活到180歲,代碼如下:


public class Constant{

//定義人類壽命極限

public fnal static int MAX_AGE=180;

}


然後重新編譯:javac Constant,編譯完成後執行:java Client,大家想看看輸出的極限年齡是多少歲嗎?

輸出的結果是:「人類壽命極限是:150」,竟然沒有改變為180,太奇怪了,這是為何?

原因是:對於final修飾的基本類型和String類型,編譯器會認為它是穩定態(Immutable Status),所以在編譯時就直接把值編譯到字節碼中了,避免了在運行期引用(Run-time Reference),以提高代碼的執行效率。針對我們的例子來說,Client類在編譯時,字節碼中就寫上了「150」這個常量,而不是一個地址引用,因此無論你後續怎麼修改常量類,只要不重新編譯Client類,輸出還是照舊。

而對於final修飾的類(即非基本類型),編譯器認為它是不穩定態(Mutable Status),在編譯時建立的則是引用關係(該類型也叫做Soft Final),如果Client類引入的常量是一個類或實例,即使不重新編譯也會輸出最新值。

千萬不可小看了這點知識,細坑也能絆倒大象,比如在一個Web項目中,開發人員修改一個final類型的值(基本類型),考慮到重新發佈風險較大,或者是時間較長,或者是審批流程過於繁瑣,反正是為了偷懶,於是直接採用替換class類文件的方式發佈。替換完畢後應用服務器自動重啟,然後簡單測試一下(比如本類引用final類型的常量),一切OK。可運行幾天後發現業務數據對不上,有的類(引用關係的類)使用了舊值,有的類(繼承關係的類)使用的是新值,而且毫無頭緒,讓人一籌莫展,其實問題的根源就在於此。

恩,還有個小問題沒有說明,我們的例子為什麼不在IDE工具(比如Eclipse)中運行呢?那是因為在IDE中不能重現該問題,若修改了Constant類,IDE工具會自動編譯所有的引用類,「智能」化屏蔽了該問題,但潛在的風險其實仍然存在。

注意 發佈應用系統時禁止使用類文件替換方式,整體WAR包發佈才是萬全之策。