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