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