標籤:
在class檔案中,“常量池”是最複雜也最值得關注的內容。
Java是一種動態串連的語言,常量池的作用非常重要,常量池中除了包含代碼中所定義的各種基本類型(如int、long等等)和對象型(如String及數組)的常量值還,還包含一些以文本形式出現的符號引用,比如:
類和介面的全限定名;
欄位的名稱和描述符;
方法和名稱和描述符。
在C語言中,如果一個程式要調用其它庫中的函數,在串連時,該函數在庫中的位置(即相對於庫檔案開頭的位移量)會被寫在程式中,在運行時,直接去這個地址調用函數;
而在Java語言中不是這樣,一切都是動態。編譯時間,如果發現對其它類方法的調用或者對其它類欄位的引用的話,記錄進class檔案中的,只能是一個文本形式的符號引用,在串連過程中,虛擬機器根據這個文本資訊去尋找對應的方法或欄位。
所以,與Java語言中的所謂“常量”不同,class檔案中的“常量”內容很非富,這些常量集中在class中的一個地區存放,一個緊接著一個,這裡就稱為“常量池”。
java中的常量池技術,是為了方便快捷地建立某些對象而出現的,當需要一個對象時,就可以從池中取一個出來(如果池中沒有則建立一個),則在需要重複重複建立相等變數時節省了很多時間。常量池其實也就是一個記憶體空間,不同於使用new關鍵字建立的對象所在的堆空間。本文只從java使用者的角度來探討java常量池技術,並不涉及常量池的原理及實現方法。個人認為,如果是真的專註java,就必須對這些細節方面有一定的瞭解。但知道它的原理和具體的實現方法則不是必須的。
1,常量池中對象和堆中的對象
public class Test{ Integer i1=new Integer(1); Integer i2=new Integer(1); //i1,i2分別位於堆中不同的記憶體空間 System.out.println(i1==i2);//輸出false
Integer i3=1; Integer i4=1; //i3,i4指向常量池中同一個記憶體空間 System.out.println(i3==i4);//輸出true //很顯然,i1,i3位於不同的記憶體空間 System.out.println(i1==i3);//輸出false}
2,8種基本類型的封裝類和對象池
java中基本類型的封裝類的大部分都實現了常量池技術,這些類是Byte,Short,Integer,Long,Character,Boolean,另外兩種浮點數類型的封裝類則沒有實現。另外Byte,Short,Integer,Long,Character這5種整型的封裝類也只是在對應值小於等於127時才可使用對象池,也即對象不負責建立和管理大於127的這些類的對象。以下是一些對應的測試代碼:
public class Test{public static void main(String[] args){ //5種整形的封裝類Byte,Short,Integer,Long,Character的對象, //在值小於127時可以使用常量池 Integer i1=127; Integer i2=127; System.out.println(i1==i2)//輸出true //值大於127時,不會從常量池中取對象 Integer i3=128; Integer i4=128; System.out.println(i3==i4)//輸出false //Boolean類也實現了常量池技術 Boolean bool1=true; Boolean bool2=true; System.out.println(bool1==bool2);//輸出true //浮點類型的封裝類沒有實現常量池技術 Double d1=1.0; Double d2=1.0; System.out.println(d1==d2)//輸出false }}
3,String也實現了常量池技術
String類也是java中用得多的類,同樣為了建立String對象的方便,也實現了常量池的技術,測試代碼如下:
public class Test{public static void main(String[] args){ //s1,s2分別位於堆中不同空間 String s1=new String("hello"); String s2=new String("hello"); System.out.println(s1==s2)//輸出false //s3,s4位於池中同一空間 String s3="hello"; String s4="hello"; System.out.println(s3==s4);//輸出true}}
Integer的封裝:
public static Integer valueOf(int i) { final int offset = 128; if (i >= -128 && i <= 127) { // must cache return IntegerCache.cache[i + offset]; } return new Integer(i); }
當你直接給一個Integer對象一個int值的時候,其實它調用了valueOf方法,然後你賦的這個值很特別,是128,那麼沒有進行cache方法,相當於new了兩個新對象。所以問題中定義a、b的兩句代碼就類似於:
Integer a = new Integer(128);Integer b = new Integer(128);
這個時候再問你,輸出結果是什嗎?你就知道是false了。如果把這個數換成127,再執行:
Integer a = 127;Integer b = 127;
System.out.println(a == b);
結果就是:true
由上可知,我們進行對象比較時最好還是使用equals,便於按照自己的目的進行控制。
看一下IntegerCache這個類裡面的內容:
private static class IntegerCache { private IntegerCache() { } static final Integer cache[] = new Integer[-(-128) + 127 + 1]; static { for (int i = 0; i < cache.length; i++) cache[i] = new Integer(i - 128); } }
由於cache[]在IntegerCache類中是靜態數組,也就是只需要初始化一次,即static{......}部分,所以,如果Integer對象初始化時是-128~127的範圍,就不需要再重新定義申請空間,都是同一個對象---在IntegerCache.cache中,這樣可以在一定程度上提高效率。
String的intern()方法會尋找在常量池中是否存在一份equal相等的字串,
如果有則返回一個引用,沒有則添加自己的字串進進入常量池,
注意,只是字串部分,
所以這時會存在2份拷貝,常量池的部分被String類私人持有並管理,自己的那份按對象生命週期繼續使用.
API解釋:
返回字串對象的正常化表示形式。
一個初始時為空白的字串池,它由類 String 私人地維護。
當調用 intern 方法時,如果池已經包含一個等於此 String 對象的字串(該對象由 equals(Object) 方法確定),則返回池中的字串。否則,將此 String 對象添加到池中,並且返回此 String 對象的引用。
它遵循對於任何兩個字串 s 和 t,若且唯若 s.equals(t) 為 true 時,s.intern() == t.intern() 才為 true。
所有字面值字串和字串賦值常量運算式都是內部的。字串字面值在《Java Language Specification》的 §3.10.5 中已定義。
String s0= "java"; String s1=new String("java"); String s2=new String("java"); s1.intern(); s2=s2.intern(); //把常量池中“kvill”的引用賦給s2 System.out.println( s0==s1);//false intern返回的引用沒有引用變數接收~ s1.intern();等於廢代碼. System.out.println( s0==s1.intern() );//true System.out.println( s0==s2 );//true
String s1=new String("java"); String s2=s1.intern();//s1 檢查常量池 發現沒有 就拷貝自己的字串進去 //s2 引用該字串常量池的地址 System.out.println(s2 == s1);//false System.out.println( s2==s1.intern());//true System.out.println( s1==s1.intern());// false最後引出equals()和==的故事對於字串比較,一定要切記使用equals,勿用==
Java基礎-常量池