第一節 String類型的方法參數
運行下面這段代碼,其結果是什么?
package com.test; public class Example { String str = new String("good"); char[] ch = { 'a', 'b', 'c' }; public static void main(String[] args) { Example ex = new Example(); ex.change(ex.str, ex.ch); System.out.println(ex.str); System.out.println(ex.ch); } public void change(String str, char ch[]) { str = "test ok"; ch[0] = 'g'; } }
結果如下:
good
gbc
解說:java 中String是 immutable的,也就是不可變,一旦初始化,引用指向的內容是不可變的(注意:是內容不可變)。
我們再來看下面這段代碼,它的運行結果是什么?
package com.test; public class Example { String str = new String("good"); char[] ch = { 'a', 'b', 'c' }; public static void main(String[] args) { Example ex = new Example(); ex.change(ex.str, ex.ch); System.out.println(ex.str); System.out.println(ex.ch); } public void change(String str, char ch[]) { str = str.toUpperCase(); ch = new char[]{ 'm', 'n' }; } }
結果如下:
good
abc
結合前面的解釋進行理解,這個結果是不是在意料之中?!
根據JDK中java.lang.String的源碼進行分析,從中可以得出String類型的對象不可變的原因,大致上有如下兩個:
1、java.lang.String類型在實現時,其內部成員變量全部使用final來修飾,保證成員變量的引用值只能通過構造函數來修改;
2、java.lang.String類型在實現時,在外部可能修改其內部存儲值的函數實現中,返回時一律構造新的String對象或者新的byte數組或者char數組;
僅憑第1點還不能保證其不可變特性:假如通過String類型的toCharArray方法可以直接訪問String類型內部定義的char數組,那么即便String類型內部的char數組使用了final來修飾,也僅僅保證這個成員變量的引用不可變,而無法保證引用指向的內存區域不可變。
第2點保證了外部不可能修改java.lang.String類型對象的內部屬性,從而保證String對象是不可變的。
第二節 String類型變量的賦值
2.1 String變量賦值方式:s2=new String(s1)
下面這段代碼的運行結果是什么
package com.soft; public class ExecutorsDemo { public static void main(String[] args) { String s1="abc"+"def"; String s2=new String(s1); if(s1.equals(s2)) System.out.println("equals succeeded"); if(s1==s2) System.out.println("==succeeded"); } }
結果:
equals succeeded
解說:上述代碼中,s1與s2指向不同的對象,但是兩個對象的內容卻是一樣的,故“s1==s2”為假,s1.equals(s2)為真。
此處我們來細說一下"=="與equals的作用:
(1)"=="操作符的作用
A、用於基本數據類型的比較
B、判斷引用是否指向堆內存的同一塊地址
(2)equals的作用
用於判斷兩個變量是否是對同一個對象的引用,即堆中的內容是否相同,返回值為布爾類型
2.2 String變量賦值方式:s2 = s1
package com.soft; public class ExecutorsDemo { public static void main(String[] args) { String s1 = new String("java"); String s2 = s1; System.out.println(s1==s2); System.out.println(s1.equals(s2)); } }
結果:
true true
解說:如果理解了前面那個例子的運行情況,那么這個就是一目了然的事情,此處s1與s2指向同一個對象,"=="操作符的作用之一就是判斷引用是否指向堆內存的同一塊地址,equals的作用是判斷兩個變量是否是對同一個對象的引用(即堆中的內容是否相同),故此處均輸出“true”
第三節 將字符數組或字符串數組轉換為字符串
此處再補充兩個應用場景
一、將字符數組轉換為字符串
下面代碼中的兩種方式均可直接將字符數組轉換為字符串,不需要遍歷拼接
package com.test; public class Main { public Main() { } public static void main(String[] args) { char[] data = {'a', 'b', 'c'}; // String str = new String(data); String str = String.valueOf(data); System.out.println(str); } }
此處可以看一下其他作者的文章以深入理解:【Java】數組不能通過toString方法轉為字符串 http://www.cnblogs.com/ningvsban/p/3955483.html
二、將字符串數組轉換為字符串
下面的代碼是我們常用的方式,循環拼接
package com.test; public class Main { public Main() { } public static void main(String[] args) { String[] ary = {"abc", "123", "45"}; String s = ""; for(String temp : ary) { s=s.concat(temp);//和下面的一行二選一即可 // s += temp; } System.out.println(s); } }
上述代碼段不需要過多解釋了
第四節 StringBuffer和StringBuilder
提到String,就不得不提一下JDK中另外兩個常用來表示字符串的類,StringBuffer和StringBuilder。在編寫java代碼的過程中有時要頻繁地對字符串進行拼接,如果直接用“+”拼接的話會建立很多的String型對象,嚴重的話會對服務器資源和性能造成不小的影響;而使用StringBuilder和StringBuffer能解決以上問題。根據注釋,StringBuffer可謂老資格了,從JDK1.0時即伴隨Java征戰世界,而StringBuilder直到JDK1.5時才出現。面試時,StringBuffer和StringBuilder的區別也是常問的話題,StringBuffer是線程安全的,而StringBuilder不是線程安全的。
一、StringBuffer和StringBuilder的共同點:
1、用來完成字符串拼接操作;
2、都是可變對象,對象內的字符緩存會隨着拼接操作而動態擴展;
3、構造時傳入內部緩存大小時,可以降低緩存擴展的次數,明顯提升字符串拼接操作的效率;
二、StringBuffer和StringBuilder的區別:
1、StringBuilder的方法都是線程不安全的,從另外一個角度講,StringBuilder類型的對象在做字符串拼接操作時,由於少了線程同步的操作,執行效率上有很大提升;
2、StringBuffer的方法都加上了synchronized關鍵字,因而在一定的場景下,StringBuffer類型的對象都是線程安全的,但在執行效率上,由於多了線程同步的操作,因而會有少許的損失;
在大多數場景下,字符串拼接操作都是不需要考慮多線程環境下對結果的影響的,因而使用StringBuilder類型可以提升代碼的執行效率。
在多個線程的代碼中共享同一個StringBuffer類型的對象時,需要關注synchronized關鍵字對最終結果的影響。由於StringBuffer類的實現中,僅僅對每個方法使用了synchronized修飾,這只能保證在多線程場景下,訪問StringBuffer對象的同一個方法時可以保證最終結果的一致性,假如一個線程訪問A方法,另外一個線程方法B方法,則由於加鎖對象的不同,可能會出現不一致的現象,這是需要程序員特別要注意的地方。類似的,可以參考Vector的實現和應用場景。
針對上面的將字符串數組轉換為字符串,可以借助上面提到的StringBuilder(當然StringBuffer也可以),代碼如下:
package com.test; public class Main { public Main() { } public static void main(String[] args) { String[] ary = {"abc", "123", "45"}; StringBuilder sb = new StringBuilder(); for(int i = 0; i < ary.length; i++){ sb. append(ary[i]); } String newStr = sb.toString(); System.out.println(newStr); } }
參考資料
這里有兩篇文章,值得一讀:
(1)三分鍾理解Java中字符串(String)的存儲和賦值原理 http://blog.csdn.net/zhuiwenwen/article/details/12351565
(2)Java之內存分析和String對象 http://www.cnblogs.com/devinzhang/archive/2012/01/25/2329463.html