????????????????????? ????????? inconstant constants ( 變化無常的常量 )??
??????????????????????????????????????????
??????????????????????????????????????????
馬嘉楠
?????????????? 2006-10-18
看到這個題目也許你會感到奇怪,會想我在胡說八道什么,一定又是起個怪異的名字,騙取點擊率。還請你耐心看完,如果你有所收獲,那么我很高興;如果你還是覺得上當了,那我繼續努力寫出點有用的東西,呵呵。
其實我想了很久,也還是不知道起一個什么題目好,就套用了《 The Java Language Specification 》中的一個名詞“ inconstant constants”,我把他翻譯成“變化無常的常量”
注:部分內容在《
使用Java中的final變量需要注意的地方
》有提到,不過我轉載的原文不夠詳細深入,這才重新寫一下。
我們還是來先看一段代碼,由代碼引出問題:
public
class
?ClassX?{
???
public
static
final
int
??X??
=
???
2
?;
}
public
class
ClassTest?{
???
public
static
void
main(String[]?args){
??????System.out.println(ClassX.X);
???}
} ?
輸出結果:
2
結果是顯而易見的,這里需要說明的是:
根據Java語言規范,對于java中的static final變量,如果用一個在編譯期間(complie time)可以計算出結果的表達式進行初始化,則用到此變量的地方會被該表達式的結果所替代。本例中,在編譯期間,ClassTest.main() 函數中 ClassX.X 將被2所替代。
此時,在類ClassTest main()中不再有指向ClassX的動態鏈接,告訴ClassTest在運行的時候從ClassX獲得X的值,你可以通過使用javap反編譯器幫助你理解。
1. 先編譯ClassTest.java文件
????????????javac ClassTest.java
2. 使用javap
????????????javap -c ClassTest
屏幕輸出:
Complied?from?"ClassTest.java"
public?class?ClassTest?extends?java.lang.Object{
public?ClassTest();
???Code:
??????0:?aload_0
??????1:?invokespecial?????#1;?//Method?java/lang/Object."<init>
":()V
??????4:?return
public?static?void?main(java.lang.String[]);
???Code:
??????0:?getstatic?????????#2;?//Field?java/lang/System.out:Ljava/io/PrintStream;
??????3:?iconst_2?
??????4:?invokevirtua??????#3;?//Method?java/io/PrintStream.println:(I)V
??????7:?return
}
可以看出,在調用System.out.println()之前,整數2已經被放在JVM的堆棧中,不再有指向ClassX.X的鏈接。如果此時,改變ClassX.X的值為1,并且重新編譯ClassX.X文件,但是并不重新編譯ClassTest.java文件,運行ClassTest,輸出結果仍然是2.
這么做(常量替換)的一個原因是為了在編譯期間檢查switch case語句。switch語句中的每一個case都需要一個常量值,而且每兩個之間都不能相同,編譯器在編譯期間將會做檢查。
如果用來給static final變量進行初始化的表達式,只能在運行時刻才可以計算出值,那么常量替換就不會發生.例如:
public
class
ClassX?{
???public
static?final?int?X?=?new?
java.util.Random().nextInt();
} ?
ClassX 改變了,我們再來看一下Main.main():
Complied?from?"ClassTest.java"
public?class?ClassTest?extends?java.lang.Object{
public?ClassTest();
???Code:
??????0:?aload_0
??????1:?invokespecial??????#1;?//Method?java/lang/Object."<init>
":()V
??????4:?return
public?static?void?main(java.lang.String[]);
???Code:
??????0:?getstatic????????? #2;?//Field?java/lang/System.out:Ljava/io/PrintStream;
??????3:?getstatic????????? #3;?//Field?ClassX.X:I?
??????6:?invokevirtual??????#4;?//Method?java/io/PrintStream.println:(I)V
??????9:?return
}
此時我們可以看見有個引用指向了Field X。
( 如果把類ClassX改成Interface,仍然會出現上面的結果 )
當然有方法可以使你避免出現"inconstant constants"問題。
第一種方法:
當你要聲明一個編譯期間常量的時候,一定要保證此變量不會或者不太可能改變,或者盡量少使用聲明為static final的變量。當然這只能治標不能治本,所以我推薦使用第二種方法。
第二種方法:
將變量聲明為private,同時聲明一個方法來獲得此變量的值
//
ClassX.java修改如下:?
public
class?
ClassX?{
???private?static?final?int?X?=?2
;
???public?
static
int
getX(){
??????return
X;
???}
}
//
ClassTest.java修改如下:?
public?class?
ClassTest{
???public?static?void?
main(String[]?args){
??????System.out.println(ClassX.getX());
???}
}
此時再改變ClassX.X的值為1,重新編譯ClassX.java,而不編譯ClassTest,結果就會顯示1,而非2。這就避免了"inconstant constants"的問題。
下一篇準備講一下
《在java中使用循環定義會出現哪些問題?》
?
馬嘉楠
jianan.ma@gmail.com
posted on 2006-10-18 16:27
馬嘉楠 閱讀(1616)
評論(5) 編輯 收藏 所屬分類:
Java