????????????????????? ????????? inconstant constants ( 變化無常的常量 )??
??????????????????????????????????????????
??????????????????????????????????????????
馬嘉楠
?????????????? 2006-10-18
看到這個(gè)題目也許你會(huì)感到奇怪,會(huì)想我在胡說八道什么,一定又是起個(gè)怪異的名字,騙取點(diǎn)擊率。還請(qǐng)你耐心看完,如果你有所收獲,那么我很高興;如果你還是覺得上當(dāng)了,那我繼續(xù)努力寫出點(diǎn)有用的東西,呵呵。
其實(shí)我想了很久,也還是不知道起一個(gè)什么題目好,就套用了《 The Java Language Specification 》中的一個(gè)名詞“ inconstant constants”,我把他翻譯成“變化無常的常量”
注:部分內(nèi)容在《
使用Java中的final變量需要注意的地方
》有提到,不過我轉(zhuǎn)載的原文不夠詳細(xì)深入,這才重新寫一下。
我們還是來先看一段代碼,由代碼引出問題:
??? public static final int ??X?? = ??? 2 ?;
}
public class ClassTest?{
??? public static void main(String[]?args){
??????System.out.println(ClassX.X);
???}
} ?
輸出結(jié)果:
2
結(jié)果是顯而易見的,這里需要說明的是:
根據(jù)Java語言規(guī)范,對(duì)于java中的static final變量,如果用一個(gè)在編譯期間(complie time)可以計(jì)算出結(jié)果的表達(dá)式進(jìn)行初始化,則用到此變量的地方會(huì)被該表達(dá)式的結(jié)果所替代。本例中,在編譯期間,ClassTest.main() 函數(shù)中 ClassX.X 將被2所替代。
此時(shí),在類ClassTest main()中不再有指向ClassX的動(dòng)態(tài)鏈接,告訴ClassTest在運(yùn)行的時(shí)候從ClassX獲得X的值,你可以通過使用javap反編譯器幫助你理解。
1. 先編譯ClassTest.java文件
????????????javac ClassTest.java
2. 使用javap
????????????javap -c ClassTest
屏幕輸出:
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
}
可以看出,在調(diào)用System.out.println()之前,整數(shù)2已經(jīng)被放在JVM的堆棧中,不再有指向ClassX.X的鏈接。如果此時(shí),改變ClassX.X的值為1,并且重新編譯ClassX.X文件,但是并不重新編譯ClassTest.java文件,運(yùn)行ClassTest,輸出結(jié)果仍然是2.
這么做(常量替換)的一個(gè)原因是為了在編譯期間檢查switch case語句。switch語句中的每一個(gè)case都需要一個(gè)常量值,而且每?jī)蓚€(gè)之間都不能相同,編譯器在編譯期間將會(huì)做檢查。
如果用來給static final變量進(jìn)行初始化的表達(dá)式,只能在運(yùn)行時(shí)刻才可以計(jì)算出值,那么常量替換就不會(huì)發(fā)生.例如:
???public static?final?int?X?=?new? java.util.Random().nextInt();
} ?
ClassX 改變了,我們?cè)賮砜匆幌翸ain.main():
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
}
此時(shí)我們可以看見有個(gè)引用指向了Field X。
( 如果把類ClassX改成Interface,仍然會(huì)出現(xiàn)上面的結(jié)果 )
當(dāng)然有方法可以使你避免出現(xiàn)"inconstant constants"問題。
第一種方法:
當(dāng)你要聲明一個(gè)編譯期間常量的時(shí)候,一定要保證此變量不會(huì)或者不太可能改變,或者盡量少使用聲明為static final的變量。當(dāng)然這只能治標(biāo)不能治本,所以我推薦使用第二種方法。
第二種方法:
將變量聲明為private,同時(shí)聲明一個(gè)方法來獲得此變量的值
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());
???}
}
此時(shí)再改變ClassX.X的值為1,重新編譯ClassX.java,而不編譯ClassTest,結(jié)果就會(huì)顯示1,而非2。這就避免了"inconstant constants"的問題。
下一篇準(zhǔn)備講一下
《在java中使用循環(huán)定義會(huì)出現(xiàn)哪些問題?》
?
馬嘉楠
jianan.ma@gmail.com