以下程序編譯環境爲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
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