神奇的常量池和intern方法

以下程序編譯環境爲JDK1.8,且是對於複合數據類型而言,不針對原始數據類型如byte\int\short\float\double等

一、引入

我們也許會被灌輸一個觀念,就是複合數據類型比較要用equals,不能用==,否則會出現值相等和地址不相等導致錯誤。
讓我們思考網上的兩個有趣的例子

/*****   exmple1 ******/
Integer i1 = 1; 
Integer i2 = 1;
System.out.println(i1 == i2);//true

Integer i3 = 100;
Integer i4 = 100;
System.out.println(i3 == i4);//true 

Integer i5 = 127;
Integer i6 = 127;
System.out.println(i5 == i6);//true

Integer i5n = new Integer(127);
Integer i6n = new Integer(127);
System.out.println(i5n == i6n);//false

Integer i7 = 128;
Integer i8 = 128;
System.out.println(i7 == i8);//false

Integer i9 = 1000;
Integer i10 = 1000;
System.out.println(i9 == i10);//false


/*****   exmple2 比較上下兩段代碼******/
//  String str1 = new String("1") + new String("1");        
//  System.out.println(str1.intern() == str1);  //true   

String str1 = new String("1") ;        
System.out.println(str1.intern() == str1); //false
System.out.println(str1 == "1");  //false  

這裏用了==比較,但也許疑惑,爲什麼的1、100、127會有==,而new、128、1000會不==,這是偶然還是必然;爲什麼example2的上下兩段代碼的運行結果會不一樣,難道受”+”影響了?intern()返回值是什麼……

二、intern()和常量池

intern()

對於JDK7以上的string調用intern方法,會string的值與常量池比對,若不存在則創建常量池變量,無論成功與否都會返回常量池的引用。

常量池

常量池,顧名思義,就是將對象放入池中,使用時通過對象引用的方式。
好處是避免頻繁的創建和銷燬對象而影響系統性能,其實現了對象的共享。
那我們在編程中如何使用常量池呢?

  • public final static
    我們也許用過這個聲明過全局靜態變量

  • 對象創建方式

  String str1 = "abcd";
  String str2 = new String("abcd");
  System.out.println(str1==str2);//false

第一種是,在常量池中尋找 “abcd”,若有則返回引用,若無則創建常量池對象並返回引用;第二種方式是直接在堆內存空間創建一個新的對象。所以str1和str2會指向不同的地址
若改爲下面

  String str1 = "abcd";
  String str2 = "abcd";
  System.out.println(str1==str2);//true

因爲str2在常量池中會找到”abcd”

  • 連接表達式 +
String str1 = "cunteng";
String str2 = "008";

String str3 = "cunteng" + "008";
String str4 = str1 + str2;
System.out.println(str3 == str4);//false

String str5 = "cunteng008";
System.out.println(str3 == str5);//true

原因是常量和常量相加爲常量,二變量str1+str2在編譯時期是無法確定的,所以str4不會到常量池檢查是否罕有該常量
注意特例: public static final 創建的常量與文本創建的
如”cunteng”是一樣的

public class Main {
    final static String str1 = "cunteng";
    final static String str2 = "008";
    public static void main(String[] args) throws Exception {  
        String str3 = "cunteng" + "008";
        String str4 = (str1 + str2);
        System.out.println(str3 == str4);//true

        String str5 = "cunteng008";
        System.out.println(str3 == str5);//true

    }

}
  • String s1 = new String(“cunteng008”);發生了什麼??
    每次new String(“cunteng008”),都會產生一個新的對象;且將”cunteng008”常量池的對象比較,若不存在,則在常量池創建新對象,我覺得它相當於暗中進行了一次”cunteng008”.intern();
    看一個有趣的例子
/** example3 **/
String str1 = new String("cunteng") + new String("008");      
System.out.println(str1.intern() == str1);  //true 

/** example4 **/
String str1 = new String("cunteng") ;      
System.out.println(str1.intern() == str1);  //false

對於example3,new String(“cunteng”)和new String(“008”)都會分別產生一個對象,並將”cunteng”和”008”放入常量池中;但 new String(“cunteng”)整體並不是一個常量,兩個這樣的對象會產生一個新的對象;當str1.intern()時,常量池中只有”cunteng”和”008”,並沒有”cunteng008”,所以常量池會新增”cunteng008”,但不會新創建對象,而是直接引用str1;
new String(“cunteng”)後,常量池已經有了”cunteng”,是常量池新創建的,所以與str1指向自然不同。

  • 使用了常量池的基本類型包裝類
    • Integer
      -128 ~ 127 裝箱時使用了常量池,不會產生新的對象,二超範圍的會。
Integer i = A; //A爲常量
等價於
Integer i = Integer.valueOf(A);
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
    return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}
private static class IntegerCache {
   static final int high;
   static final Integer cache[];

      static {
           final int low = -128;

           // high value may be configured by property
           int h = 127;
           if (integerCacheHighPropValue != null) {
     // Use Long.decode here to avoid invoking methods that
    // require Integer's autoboxing cache to be initialized
               int i = Long.decode(integerCacheHighPropValue).intValue();
               i = Math.max(i, 127);
               // Maximum array size is Integer.MAX_VALUE
               h = Math.min(i, Integer.MAX_VALUE - -low);
           }
           high = h;

           cache = new Integer[(high - low) + 1];
           int j = low;
           for(int k = 0; k < cache.length; k++)
               cache[k] = new Integer(j++);
       }

       private IntegerCache() {}
   }
  • Bool
public static Boolean valueOf(boolean b) {
        return (b ? TRUE : FALSE);
    }
    /**
     * The {@code Boolean} object corresponding to the primitive
     * value {@code true}.
     */
    public static final Boolean TRUE = new Boolean(true);
    /**
     * The {@code Boolean} object corresponding to the primitive
     * value {@code false}.
     */
    public static final Boolean FALSE = new Boolean(false);

三、總結

  • intern返回的是常量池對象引用
  • new會生成一個新的對象,分配新的內存;但第一次new會指向常量池的引用
  • 常量相加等於常量,會加入到常量池;
  • 已經創建了的對象,在調用intern時,常量池不會創建新的對象,而是直接指向已創建的對象;
  • final static 爲常量;
  • 基本類的包裝類有部分使用常量池技術

在看看引入的問題,就簡單了許多

/*****   exmple1 ******/
/*
1/100/127在[-128,127]範圍內,引用常量池,故==;128/1000超出會分別創建新的對象,故不==;new對象一定會創建新對象,故不==。
*/
Integer i1 = 1; 
Integer i2 = 1;
System.out.println(i1 == i2);//true

Integer i3 = 100;
Integer i4 = 100;
System.out.println(i3 == i4);//true 

Integer i5 = 127;
Integer i6 = 127;
System.out.println(i5 == i6);//true

Integer i5n = new Integer(127);
Integer i6n = new Integer(127);
System.out.println(i5n == i6n);//false

Integer i7 = 128;
Integer i8 = 128;
System.out.println(i7 == i8);//false

Integer i9 = 1000;
Integer i10 = 1000;
System.out.println(i9 == i10);//false


/*****   exmple2 比較上下兩段代碼******/
/*
str1不會進入常量池,當str1.intern時,因爲str1已創建對象,故常量池直接指向str1;
new String("1")後常量池創建新對象並存下"1",常量池的對象與str1不同,故調用intern時返回的常量池指向的對象與str1不==;
*/
//  String str1 = new String("1") + new String("1");        
//  System.out.println(str1.intern() == str1);  //true   

String str1 = new String("1") ;        
System.out.println(str1.intern() == str1); //false
System.out.println(str1 == "1");  //false  

new會生成一個新的對象,分配新的內存;但有時候會讓它與一個基本類型比較,這時Integer會先拆箱取出值再比較,這時就是值比較了。
下面例子


int i=0;
Integer j = new Integer(0);
System.out.println(i==j); //true

參考
Java常量池理解與總結
Java面試——從JVM角度比較equals和==的區別
Java技術——你真的瞭解String類的intern()方法嗎
IntegerCache in Java

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章