equals 與 ==
初學(xué) Java 有段時(shí)間了,感覺(jué)似乎開始入了門,有了點(diǎn)兒感覺(jué)
但是發(fā)現(xiàn)很多困惑和疑問(wèn)而且均來(lái)自于最基礎(chǔ)的知識(shí)
折騰了一陣子又查了查書,終于對(duì) String 這個(gè)特殊的對(duì)象有了點(diǎn)感悟
大家先來(lái)看看一段奇怪的程序:
public class TestString {
public static void main(String[] args) {
String s1 = "Monday";
String s2 = "Monday";
}
}
這個(gè)程序真是簡(jiǎn)單?。】墒怯惺裁磫?wèn)題呢?
1. 來(lái)自 String 的憂慮
上面這段程序中,到底有幾個(gè)對(duì)象呢?
可能很多人脫口而出:兩個(gè),s1 和 s2
為什么?
String 是 final 類,它的值不可變。
看起來(lái)似乎很有道理,那么來(lái)檢測(cè)一下吧,稍微改動(dòng)一下程序
就可以看到結(jié)果了:
public class TestString {
public static void main(String[] args) {
String s1 = "Monday";
String s2 = "Monday";
if (s1 == s2)
System.out.println("s1 == s2");
else
System.out.println("s1 != s2");
}
}
呵呵,很多人都會(huì)說(shuō)已經(jīng)不止兩個(gè)對(duì)象了
編譯并運(yùn)行程序,輸出:s1 == s2
啊!
為什么 s1 == s2 ?
== 分明是在說(shuō):s1 與 s2 引用同一個(gè) String 對(duì)象 -- "Monday"!
2. 千變?nèi)f化的 String
再稍微改動(dòng)一下程序,會(huì)有更奇怪的發(fā)現(xiàn):
public class TestString {
public static void main(String[] args) {
String s1 = "Monday";
String s2 = new String("Monday");
if (s1 == s2)
System.out.println("s1 == s2");
else
System.out.println("s1 != s2");
if (s1.equals(s2))
System.out.println("s1 equals s2");
else
System.out.println("s1 not equals s2");
}
}
我們將 s2 用 new 操作符創(chuàng)建
程序輸出:
s1 != s2
s1 equals s2
嗯,很明顯嘛
s1 s2分別引用了兩個(gè)"Monday"String對(duì)象
可是為什么兩段程序不一樣呢?
3. 在 String 的游泳池中游泳
哈哈,翻了翻書終于找到了答案:
原來(lái),程序在運(yùn)行的時(shí)候會(huì)創(chuàng)建一個(gè)字符串緩沖池
當(dāng)使用 s2 = "Monday" 這樣的表達(dá)是創(chuàng)建字符串的時(shí)候,程序首先會(huì)
在這個(gè)String緩沖池中尋找相同值的對(duì)象,在第一個(gè)程序中,s1先被
放到了池中,所以在s2被創(chuàng)建的時(shí)候,程序找到了具有相同值的 s1
將 s2 引用 s1 所引用的對(duì)象"Monday"
第二段程序中,使用了 new 操作符,他明白的告訴程序:
“我要一個(gè)新的!不要舊的!”與是一個(gè)新的"Monday"Sting對(duì)象被創(chuàng)
建在內(nèi)存中。他們的值相同,但是位置不同,一個(gè)在池中游泳
一個(gè)在岸邊休息。哎呀,真是資源浪費(fèi),明明是一樣的非要分開做什么呢?
4. 繼續(xù)潛水
再次更改程序:
public class TestString {
public static void main(String[] args) {
String s1 = "Monday";
String s2 = new String("Monday");
s2 = s2.intern();
if (s1 == s2)
System.out.println("s1 == s2");
else
System.out.println("s1 != s2");
if (s1.equals(s2))
System.out.println("s1 equals s2");
else
System.out.println("s1 not equals s2");
}
}
這次加入:s2 = s2.intern();
哇!程序輸出:
s1 == s2
s1 equals s2
原來(lái),程序新建了 s2 之后,又用intern()把他打翻在了池里
哈哈,這次 s2 和 s1 有引用了同樣的對(duì)象了
我們成功的減少了內(nèi)存的占用
5. == 與 equals() 的爭(zhēng)斗
String 是個(gè)對(duì)象,要對(duì)比兩個(gè)不同的String對(duì)象的值是否相同
明顯的要用到 equals() 這個(gè)方法
可是如果程序里面有那么多的String對(duì)象,有那么多次的要用到 equals ,
哦,天哪,真慢啊
更好的辦法:
把所有的String都intern()到緩沖池去吧
最好在用到new的時(shí)候就進(jìn)行這個(gè)操作
String s2 = new String("Monday").intern();
嗯,大家都在水池里泡著了嗎?哈哈
現(xiàn)在我可以無(wú)所顧忌的用 == 來(lái)比較 String 對(duì)象的值了
真是爽啊,又快又方便!
關(guān)于String :)
String 啊 String ,讓我說(shuō)你什么好呢?
你為我們 Java 程序員帶來(lái)所有的困擾還不夠嗎?
看看 String 這一次又怎么鬧事兒吧
1. 回顧一下壞脾氣的 String 老弟
例程1:
class Str {
public static void main(String[] args) {
String s = "Hi!";
String t = "Hi!";
if (s == t)
System.out.println("equals");
else
System.out.println("not equals");
}
}
程序輸出什么呢?
如果看客們看過(guò)我的《來(lái)自 String 的困惑》之一
相信你很快會(huì)做出正確的判斷:
程序輸出:equals
2. 哦,天哪,它又在攪混水了
例程2:
class Str {
public static void main(String[] args) {
String s = "HELLO";
String t = s.toUpperCase();
if (s == t)
System.out.println("equals");
else
System.out.println("not equals");
}
}
那么這個(gè)程序有輸出什么呢?
慎重!再慎重!不要被 String 這個(gè)迷亂的家伙所迷惑!
它輸出:equals
WHY!!!
把程序簡(jiǎn)單的更改一下:
class Str2 {
public static void main(String[] args) {
String s = "Hello";
String t = s.toUpperCase();
if (s == t)
System.out.println("equals");
else
System.out.println("not equals");
}
}
你可能會(huì)說(shuō):不是一樣嗎?
不!千真萬(wàn)確的,不一樣!這一次輸出:not equals
Oh MyGOD!!!
誰(shuí)來(lái)教訓(xùn)一下這個(gè) String 啊!
3. 你了解你的馬嗎?
“要馴服脫韁的野馬,就要了解它的秉性”牛仔們說(shuō)道。
你了解 String 嗎?
解讀 String 的 API ,可以看到:
toUpperCase() 和 toLowerCase() 方法返回一個(gè)新的String對(duì)象,
它將原字符串表示字符串的大寫或小寫形勢(shì);
但是要注意:如果原字符串本身就是大寫形式或小寫形式,那么返回原始對(duì)象。
這就是為什么第二個(gè)程序中 s 和 t 糾纏不清的緣故
對(duì)待這個(gè)淘氣的、屢教不改的 String ,似乎沒(méi)有更好的辦法了
讓我們解剖它,看看它到底有什么結(jié)構(gòu)吧:
(1) charAt(int n) 返回字符串內(nèi)n位置的字符,第一個(gè)字符位置為0,
最后一個(gè)字符的位置為length()-1,訪問(wèn)錯(cuò)誤的位置會(huì)扔出一塊大磚頭:
StringIndexOutOfBoundsException 真夠大的
(2) concat(String str) 在原對(duì)象之后連接一個(gè) str ,但是返回一個(gè)新的 String 對(duì)象
(3) EqualsIgnoreCase(String str) 忽略大小寫的 equals 方法
這個(gè)方法的實(shí)質(zhì)是首先調(diào)用靜態(tài)字符方法toUpperCase() 或者 toLowerCase()
將對(duì)比的兩個(gè)字符轉(zhuǎn)換,然后進(jìn)行 == 運(yùn)算
(4) trim() 返回一個(gè)新的對(duì)象,它將原對(duì)象的開頭和結(jié)尾的空白字符切掉
同樣的,如果結(jié)果與原對(duì)象沒(méi)有差別,則返回原對(duì)象
(5) toString() String 類也有 toString() 方法嗎?
真是一個(gè)有趣的問(wèn)題,可是如果沒(méi)有它,你的 String 對(duì)象說(shuō)不定真的不能用在
System.out.println() 里面啊
小心,它返回對(duì)象自己
String 類還有很多其他方法,掌握他們會(huì)帶來(lái)很多方便
也會(huì)有很多困惑,所以堅(jiān)持原則,是最關(guān)鍵的
4. 我想買一匹更好的馬
來(lái)購(gòu)買更馴服溫和的 String 的小弟 StringBuffer 吧
這時(shí)候會(huì)有人反對(duì):它很好用,它效率很高,它怎么能夠是小弟呢?
很簡(jiǎn)單,它的交互功能要比 String 少,如果你要編輯字符串
它并不方便,你會(huì)對(duì)它失望
但這不意味著它不強(qiáng)大
public final class String implements Serializable, Comparable, CharSequence
public final class StringBuffer implements Serializable, CharSequence
很明顯的,小弟少了一些東東,不過(guò)這不會(huì)干擾它的前途
StringBuffer 不是由 String 繼承來(lái)的
不過(guò)要注意兄弟它也是 final 啊,本是同根生
看看他的方法吧,這么多穩(wěn)定可靠的方法,用起來(lái)比頑皮的 String 要有效率的多
?br /> Java 為需要改變的字符串對(duì)象提供了獨(dú)立的 StringBuffer 類
它的實(shí)例不可變(final),之所以要把他們分開
是因?yàn)?,字符串的修改要求系統(tǒng)的開銷量增大,
占用更多的空間也更復(fù)雜,相信當(dāng)有10000人擠在一個(gè)狹小的游泳池里游泳
而岸邊又有10000人等待進(jìn)入游泳池而焦急上火
又有10000人在旁邊看熱鬧的時(shí)候,你這個(gè) String 游泳池的管理員也會(huì)焦頭爛額
在你無(wú)需改變字符串的情況下,簡(jiǎn)單的 String 類就足夠你使喚的了,
而當(dāng)要頻繁的更改字符串的內(nèi)容的時(shí)候,就要借助于宰相肚里能撐船的
StringBuffer 了
5. 宰相肚里能撐船
(1) length() 與 capacity()
String 中的 length() 返回字符串的長(zhǎng)度
兄弟 StringBuffer 也是如此,他們都由對(duì)象包含的字符長(zhǎng)度決定
capacity()呢?
public class TestCapacity {
public static void main(String[] args){
StringBuffer buf = new StringBuffer("it was the age of wisdom,");
System.out.println("buf = " + buf);
System.out.println("buf.length() = " + buf.length());
System.out.println("buf.capacity() = " + buf.capacity());
String str = buf.toString();
System.out.println("str = " + str);
System.out.println("str.length() = " + str.length());
buf.append(" " + str.substring(0,18)).append("foolishness,");
System.out.println("buf = " + buf);
System.out.println("buf.length() = " + buf.length());
System.out.println("buf.capacity() = " + buf.capacity());
System.out.println("str = " + str);
}
}
程序輸出:
buf = it was the age of wisdom.
buf.length() = 25
buf.capacity() = 41
str = it was the age of wisdom
str.length() = 25
buf = it was the age of wisdom, it was the age of foolishness,
buf.length() = 56
buf.capacity() = 84
str = it was the age of wisdom,
可以看到,在內(nèi)容更改之后,capacity也隨之改變了
長(zhǎng)度隨著向字符串添加字符而增加
而容量只是在新的長(zhǎng)度超過(guò)了現(xiàn)在的容量之后才增加
StringBuffer 的容量在操作系統(tǒng)需要的時(shí)候是自動(dòng)改變的
程序員們對(duì)capacity所能夠做的僅僅是可以在初始化 StringBuffer對(duì)象的時(shí)候
以上片斷引用自http://bbs.blueidea.com/viewthread.php?tid=945875&page=###
解釋得比較形象和經(jīng)典。具體的比較,要親自動(dòng)手運(yùn)行一下程序才行,如下為網(wǎng)上找到的專門研究equals和==的關(guān)系的程序,相信可從中體會(huì)出他們的深刻不同:
String s1 = null;
String s2 = null;
System.out.println(s1==s2);//true
//System.out.println(s1.equals(s2));//NullPointerException
s1 = s2;
System.out.println(s1==s2);//true
//System.out.println(s1.equals(s2));//NullPointerException
System.out.println("***1***");
s1 = null;
s2 = "";
System.out.println(s1==s2);//false
//System.out.println(s1.equals(s2));//NullPointerException
s1 = s2;
System.out.println(s1==s2);//true
System.out.println(s1.equals(s2));//true
System.out.println("***2***");
s1 = "";
s2 = null;
System.out.println(s1==s2);//false
System.out.println(s1.equals(s2));//false
s1 = s2;
System.out.println(s1==s2);//true
//System.out.println(s1.equals(s2));//NullPointerException
System.out.println("***3***");
s1 = "";
s2 = "";
System.out.println(s1==s2);//true
System.out.println(s1.equals(s2));//true
s1 = s2;
System.out.println(s1==s2);//true
System.out.println(s1.equals(s2));//true
System.out.println("***4***");
s1 = new String("");
s2 = new String("");
System.out.println(s1==s2);//false
System.out.println(s1.equals(s2));//true
s1 = s2;
System.out.println(s1==s2);//true
System.out.println(s1.equals(s2));//true
System.out.println("***5***");
s1 = "null";
s2 = "null";
System.out.println(s1==s2);//ture
System.out.println(s1.equals(s2));//true
s1 = s2;
System.out.println(s1==s2);//true
System.out.println(s1.equals(s2));//true
System.out.println("***6***");
s1 = new String("null");
s2 = new String("null");
System.out.println(s1==s2);//flase
System.out.println(s1.equals(s2));//true
s1 = s2;
System.out.println(s1==s2);//true
System.out.println(s1.equals(s2));//true
System.out.println("***7***");
s1 = "abc";
s2 = "abc";
System.out.println(s1==s2);//ture
System.out.println(s1.equals(s2));//true
s1 = s2;
System.out.println(s1==s2);//true
System.out.println(s1.equals(s2));//true
System.out.println("***8***");
s1 = new String("abc");
s2 = new String("abc");
System.out.println(s1==s2);//false
System.out.println(s1.equals(s2));//true
s1 = s2;
System.out.println(s1==s2);//true
System.out.println(s1.equals(s2));//true
System.out.println("***9***");
總結(jié): 多數(shù)情況下這兩者的區(qū)別就是究竟是對(duì)對(duì)象的引用進(jìn)行比較還是對(duì)對(duì)象的值進(jìn)行比較(其他特殊情況此處不予考慮)。==操作符是比較的對(duì)象的引用而不是對(duì)象的值。
但在最初的Object對(duì)象中的equals方法與==操作符完成功能是相同的。
源碼:
java.lang.Object.equals()方法:
-------------------------------------------------------------
public boolean equalss(Object obj) {
return (this = = obj);
}
-------------------------------------------------------------
jdk文檔中給出如下解釋:
-------------------------------------------------------------
The equalss method implements an equivalence relation:
· It is reflexive: for any reference value x, x.equalss(x) should return true.
· It is symmetric: for any reference values x and y, x.equalss(y) should return true if and only if y.equalss(x) returns true.
· It is transitive: for any reference values x, y, and z, if x.equalss(y) returns true and y.equalss(z) returns true, then x.equalss(z) should return true.
· It is consistent: for any reference values x and y, multiple invocations of x.equalss(y) consistently return true or consistently return false, provided no information used in equalss comparisons on the object is modified.
· For any non-null reference value x, x.equalss(null) should return false.
The equalss method for class Object implements the most discriminating possible equivalence relation on objects; that is, for any reference values x and y, this method returns true if and only if x and y refer to the same object (x==y has the value true).
Note that it is generally necessary to override the hashCode method whenever this method is overridden, so as to maintain the general contract for the hashCode method, which states that equals objects must have equals hash codes.
-------------------------------------------------------------
對(duì)于String類的equals方法是對(duì)什么內(nèi)容進(jìn)行比較的呢?下面我們來(lái)看它的代碼和注釋:
源代碼:
-------------------------------------------------------------
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = count;
if (n == anotherString.count) {
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n-- != 0) {
if (v1[i++] != v2[j++])
return false;
}
return true;
}
}
return false;
}
-------------------------------------------------------------
此方法的注釋為:
-------------------------------------------------------------
Compares this string to the specified object. The result is true if and only if the argument is not null and is a String object that represents the same sequence of characters as this object.
-------------------------------------------------------------
由上面的代碼和注釋可以得到String類的equal方法是對(duì)對(duì)象的值進(jìn)行比較。
根據(jù)以上的討論可以得出結(jié)論:equal方法和==操作符是否存在區(qū)別要個(gè)別對(duì)待,要根據(jù)equal的每個(gè)實(shí)現(xiàn)情況來(lái)具體判斷。
posted on 2008-06-11 22:36 JavaSuns 閱讀(738) 評(píng)論(0) 編輯 收藏