java中String類的使用頻率非常高,本人在學習此模塊時,認為下列幾點知識值得注意:
一、String是不可變對象
java.lang.String類使用了final修飾,不能被繼承。Java程序中的所有字面值,即雙引號括起的字符串,如"abc",都是作為String類的實例實現的。String是常量,其對象一旦構造就不能再被改變。換句話說,String對象是不可變的,每一個看起來會修改String值的方法,實際上都是創造了一個全新的String對象,以包含修改后的字符串內容。而最初的String對象則絲毫未動。String對象具有只讀特性,指向它的任何引用都不可能改變它的值,因此,也不會對其他的引用有什么影響。但是字符串引用可以重新賦值。java字符串在內存中采用unicode編碼方式,任何一個字符對應兩個字節的定長編碼,即任何一個字符(無論中文還是英文)都算一個字符長度,占用兩個字節。
例1:
public class Immutable { public static String upcase(String s) { return s.toUpperCase(); } public static void main(String[ ] args) { String str1= "Hello World"; System.out.println(str1); //Hello World String str2 = upcase(str1); System.out.println(str2); //HELLO WORLD System.out.println(str1); //Hello World } }
當把str1傳遞給upcase()方法時,實際傳遞的是引用的一個拷貝。其實,每當把String對象作為方法的參數時,都會復制一份引用,而該引用所指的對象其實一直待在單一的物理位置上,從未動過。
二、String常量池
常量池(constant pool)指的是在編譯期被確定,並被保存在已編譯的.class文件中的一些數據。它包括了關於類、方法、接口等中的常量,也包括字符串常量。Java為了提高性能,靜態字符串(字面量/常量/常量連接的結果)在常量池中創建,並盡量使用同一個對象,重用靜態字符串。對於重復出現的字符串直接量,JVM會首先在常量池中查找,如果常量池中存在即返回該對象。
例2:
public class test1 { public static void main(String[] args){ String str1 = "Hello"; //不會創建新的String對象,而是使用常量池中已有的"Hello", String str2 = "Hello"; System.out.println(str1 == str2); //true //使用new關鍵字會創建新的String對象 String str3 = new String("Hello"); System.out.println(str1 == str3); //false } }
三、String、StringBuffer和StringBuilder的區別
1.對象的可變與不可變
String類中使用字符數組來保存數據,因為有“final”修飾符,所以string對象是不可變的。如下:
private final char value[];
StringBuilder與StringBuffer都繼承自AbstractStringBuilder類,在AbstractStringBuilder中也是使用字符數組保存數據,這兩種對象都是可變的。如下:
char[] value;
2.是否是線程安全
String中的對象是不可變的,也就可以理解為常量,所以是線程安全。
AbstractStringBuilder是StringBuilder與StringBuffer的公共父類,定義了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。
StringBuffer對方法加了同步鎖或者對調用的方法加了同步鎖,所以是線程安全的。看如下源碼:
1 public synchronized StringBuffer reverse() { 2 super.reverse(); 3 return this; 4 } 5 6 public int indexOf(String str) { 7 return indexOf(str, 0); //存在 public synchronized int indexOf(String str, int fromIndex) 方法 8 }
StringBuilder並沒有對方法進行加同步鎖,所以是非線程安全的。
3.StringBuilder與StringBuffer共同點
StringBuilder與StringBuffer有公共的抽象父類AbstractStringBuilder。
抽象類與接口的一個區別是:抽象類中可以定義一些子類的公共方法,子類只需要增加新的功能,不需要重復寫已經存在的方法;而接口中只是對方法的申明和常量的定義。
StringBuilder、StringBuffer的方法都會調用AbstractStringBuilder中的公共方法,如super.append(...)。只是StringBuffer會在方法上加synchronized關鍵字,進行同步。
如果程序不是多線程的,那么使用StringBuilder效率高於StringBuffer。
四、創建了幾個對象的問題
1 String str1 = "abc"; 2 String str2 = new String("abc");
對於1中的 String str1 = "abc",首先會檢查字符串常量池中是否含有字符串abc,如果有則直接指向,如果沒有則在字符串常量池中添加abc字符串並指向它.所以這種方法最多創建一個對象,有可能不創建對象。
對於2中的String str2 = new String("abc"),首先會在堆內存中申請一塊內存存儲字符串abc,str2指向其內存塊對象。同時還會檢查字符串常量池中是否含有abc字符串,若沒有則添加abc到字符串常量池中。所以 new String()可能會創建兩個對象。
所以如果以上兩行代碼在同一個程序中,則1中創建了1個對象,2中創建了1個對象。如果將這兩行代碼的順序調換一下,則1 String str2 = new String("abc")創建了兩個對象,而2 String str1 = "abc"沒有創建對象。
看看下面的代碼創建了多少個對象:
1 String temp="apple"; 2 for(int i=0;i<1000;i++) { 3 temp=temp+i; 4 }
答案:1001個對象。
下面的代碼創建了多少個對象:
1 String temp=new String("apple") 2 for(int i=0;i<1000;i++) { 3 temp=temp+i; 4 }
答案:1002個對象。
五、String的匹配相等問題
使用String類經常需要對兩個字符串進行對比,看是否相等。有==和equals兩種選擇,這兩者方法區別很大:
1 String ok="ok"; 2 String ok1=new String("ok"); 3 System.out.println(ok==ok1);//fasle
ok指向字符串常量池,ok1指向new出來的堆內存塊,new的字符串在編譯期是無法確定的。所以輸出false。
test2:
1 String ok="apple1"; 2 String ok1="apple"+1; 3 System.out.println(ok==ok1);//true
編譯期ok和ok1都是確定的,字符串都為apple1,所以ok和ok1都指向字符串常量池里的字符串apple1。指向同一個對象,所以為true。
test3:
1 String ok="apple1"; 2 int temp=1; 3 String ok1="apple"+temp; 4 System.out.println(ok==ok1);//false
主要看ok和ok1能否在編譯期確定,ok是確定的,放進並指向常量池,而ok1含有變量導致不確定,所以不是同一個對象.輸出false。
test4:
1 String ok="apple1"; 2 final int temp=1; 3 String ok1="apple"+temp; 4 System.out.println(ok==ok1);//true
ok確定,加上final后使得ok1也在編譯期能確定,所以輸出true。
test5:
1 public static void main(String[] args) { 2 String ok="apple1"; 3 final int temp=getTemp(); 4 String ok1="apple"+temp; 5 System.out.println(ok==ok1);//false 6 } 7 8 public static int getTemp(){ 9 return 1; 10 }
ok一樣是確定的。而ok1不能確定,需要運行代碼獲得temp,所以不是同一個對象,輸出false。
String的intern()方法
當調用 intern()方法時,如果常量池中已經包含一個等於此 String 對象的字符串(該對象由 equals(Object) 方法確定),則返回常量池中的字符串;否則,將此 String 對象添加到常量池中,並且返回此 String 對象的引用。
看以下幾個例子:
test6:
public class test6 { private static String a = "ab"; public static void main(String[] args){ String s1 = "a"; String s2 = "b"; String s = s1 + s2;//+的用法 System.out.println(s == a); //flase System.out.println(s.intern() == a);// true (intern的含義) } }
test7:
1 public class test6 { 2 private static String a = new String("ab"); 3 public static void main(String[] args){ 4 String s1 = "a"; 5 String s2 = "b"; 6 String s = s1 + s2; 7 System.out.println(s == a); //flase 8 System.out.println(s.intern() == a);//false 9 System.out.println(s.intern() == a.intern()); //true 10 } 11 }