初學(xué) Java 有段時間了,感覺似乎開始入了門,有了點兒感覺
但是發(fā)現(xiàn)很多困惑和疑問而且均來自于最基礎(chǔ)的知識
折騰了一陣子又查了查書,終于對 String 這個特殊的對象有了點感悟
大家先來看看一段奇怪的程序:
public class TestString {
public static void main(String[] args) {
String s1 = "Monday";
String s2 = "Monday";
}
}
這個程序真是簡單啊!可是有什么問題呢?
1. 來自 String 的憂慮
上面這段程序中,到底有幾個對象呢?
可能很多人脫口而出:兩個,s1 和 s2
為什么?
String 是 final 類,它的值不可變。
看起來似乎很有道理,那么來檢測一下吧,稍微改動一下程序
就可以看到結(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");
}
}
呵呵,很多人都會說已經(jīng)不止兩個對象了
編譯并運行程序,輸出:s1 == s2
啊!
為什么 s1 == s2 ?
== 分明是在說:s1 與 s2 引用同一個 String 對象 -- "Monday"!
2. 千變?nèi)f化的 String
再稍微改動一下程序,會有更奇怪的發(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分別引用了兩個"Monday"String對象
可是為什么兩段程序不一樣呢?
3. 在 String 的游泳池中游泳
哈哈,翻了翻書終于找到了答案:
原來,程序在運行的時候會創(chuàng)建一個字符串緩沖池
當(dāng)使用 s2 = "Monday" 這樣的表達(dá)是創(chuàng)建字符串的時候,程序首先會
在這個String緩沖池中尋找相同值的對象,在第一個程序中,s1先被
放到了池中,所以在s2被創(chuàng)建的時候,程序找到了具有相同值的 s1
將 s2 引用 s1 所引用的對象"Monday"
第二段程序中,使用了 new 操作符,他明白的告訴程序:
“我要一個新的!不要舊的!”與是一個新的"Monday"Sting對象被創(chuàng)
建在內(nè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
原來,程序新建了 s2 之后,又用intern()把他打翻在了池里
哈哈,這次 s2 和 s1 有引用了同樣的對象了
我們成功的減少了內(nèi)存的占用
5. == 與 equals() 的爭斗
String 是個對象,要對比兩個不同的String對象的值是否相同
明顯的要用到 equals() 這個方法
可是如果程序里面有那么多的String對象,有那么多次的要用到 equals ,
哦,天哪,真慢啊
更好的辦法:
把所有的String都intern()到緩沖池去吧
最好在用到new的時候就進(jìn)行這個操作
String s2 = new String("Monday").intern();
嗯,大家都在水池里泡著了嗎?哈哈
現(xiàn)在我可以無所顧忌的用 == 來比較 String 對象的值了
真是爽啊,又快又方便!
關(guān)于String :)
String 啊 String ,讓我說你什么好呢?
你為我們 Java 程序員帶來所有的困擾還不夠嗎?
看看 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");
}
}
程序輸出什么呢?
如果看客們看過我的《來自 String 的困惑》之一
相信你很快會做出正確的判斷:
程序輸出: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");
}
}
那么這個程序有輸出什么呢?
慎重!再慎重!不要被 String 這個迷亂的家伙所迷惑!
它輸出:equals
WHY!!!
把程序簡單的更改一下:
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");
}
}
你可能會說:不是一樣嗎?
不!千真萬確的,不一樣!這一次輸出:not equals
Oh MyGOD!!!
誰來教訓(xùn)一下這個 String 啊!
3. 你了解你的馬嗎?
“要馴服脫韁的野馬,就要了解它的秉性”牛仔們說道。
你了解 String 嗎?
解讀 String 的 API ,可以看到:
toUpperCase() 和 toLowerCase() 方法返回一個新的String對象,
它將原字符串表示字符串的大寫或小寫形勢;
但是要注意:如果原字符串本身就是大寫形式或小寫形式,那么返回原始對象。
這就是為什么第二個程序中 s 和 t 糾纏不清的緣故
對待這個淘氣的、屢教不改的 String ,似乎沒有更好的辦法了
讓我們解剖它,看看它到底有什么結(jié)構(gòu)吧:
(1) charAt(int n) 返回字符串內(nèi)n位置的字符,第一個字符位置為0,
最后一個字符的位置為length()-1,訪問錯誤的位置會扔出一塊大磚頭:
StringIndexOutOfBoundsException 真夠大的
(2) concat(String str) 在原對象之后連接一個 str ,但是返回一個新的 String 對象
(3) EqualsIgnoreCase(String str) 忽略大小寫的 equals 方法
這個方法的實質(zhì)是首先調(diào)用靜態(tài)字符方法toUpperCase() 或者 toLowerCase()
將對比的兩個字符轉(zhuǎn)換,然后進(jìn)行 == 運算
(4) trim() 返回一個新的對象,它將原對象的開頭和結(jié)尾的空白字符切掉
同樣的,如果結(jié)果與原對象沒有差別,則返回原對象
(5) toString() String 類也有 toString() 方法嗎?
真是一個有趣的問題,可是如果沒有它,你的 String 對象說不定真的不能用在
System.out.println() 里面啊
小心,它返回對象自己
String 類還有很多其他方法,掌握他們會帶來很多方便
也會有很多困惑,所以堅持原則,是最關(guān)鍵的
4. 我想買一匹更好的馬
來購買更馴服溫和的 String 的小弟 StringBuffer 吧
這時候會有人反對:它很好用,它效率很高,它怎么能夠是小弟呢?
很簡單,它的交互功能要比 String 少,如果你要編輯字符串
它并不方便,你會對它失望
但這不意味著它不強(qiáng)大
public final class String implements Serializable, Comparable, CharSequence
public final class StringBuffer implements Serializable, CharSequence
很明顯的,小弟少了一些東東,不過這不會干擾它的前途
StringBuffer 不是由 String 繼承來的
不過要注意兄弟它也是 final 啊,本是同根生
看看他的方法吧,這么多穩(wěn)定可靠的方法,用起來比頑皮的 String 要有效率的多
?br /> Java 為需要改變的字符串對象提供了獨立的 StringBuffer 類
它的實例不可變(final),之所以要把他們分開
是因為,字符串的修改要求系統(tǒng)的開銷量增大,
占用更多的空間也更復(fù)雜,相信當(dāng)有10000人擠在一個狹小的游泳池里游泳
而岸邊又有10000人等待進(jìn)入游泳池而焦急上火
又有10000人在旁邊看熱鬧的時候,你這個 String 游泳池的管理員也會焦頭爛額
在你無需改變字符串的情況下,簡單的 String 類就足夠你使喚的了,
而當(dāng)要頻繁的更改字符串的內(nèi)容的時候,就要借助于宰相肚里能撐船的
StringBuffer 了
5. 宰相肚里能撐船
(1) length() 與 capacity()
String 中的 length() 返回字符串的長度
兄弟 StringBuffer 也是如此,他們都由對象包含的字符長度決定
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也隨之改變了
長度隨著向字符串添加字符而增加
而容量只是在新的長度超過了現(xiàn)在的容量之后才增加
StringBuffer 的容量在操作系統(tǒng)需要的時候是自動改變的
程序員們對capacity所能夠做的僅僅是可以在初始化 StringBuffer對象的時候
以上片斷引用自http://bbs.blueidea.com/viewthread.php?tid=945875&page=###
解釋得比較形象和經(jīng)典。具體的比較,要親自動手運行一下程序才行,如下為網(wǎng)上找到的專門研究equals和==的關(guān)系的程序,相信可從中體會出他們的深刻不同:
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ū)別就是究竟是對對象的引用進(jìn)行比較還是對對象的值進(jìn)行比較(其他特殊情況此處不予考慮)。==操作符是比較的對象的引用而不是對象的值。
但在最初的Object對象中的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.
-------------------------------------------------------------
對于String類的equals方法是對什么內(nèi)容進(jìn)行比較的呢?下面我們來看它的代碼和注釋:
源代碼:
-------------------------------------------------------------
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方法是對對象的值進(jìn)行比較。
根據(jù)以上的討論可以得出結(jié)論:equal方法和==操作符是否存在區(qū)別要個別對待,要根據(jù)equal的每個實現(xiàn)情況來具體判斷。