總結一下String(Java)

來源:互聯網
上載者:User

原帖:http://topic.csdn.net/u/20090519/18/7b8cf7ef-bc06-4d26-8a2c-692eb0562231_2.html

 

作者:zangxt  

  String類是Java中很重要的一個類,在此總結一下這個類的特別之處。下面的相關資料翻譯自《java語言規範》(第三版)和《java虛擬機器規範》(第二版),有的直接摘引了原文。下面的代碼都是用SUN jdk1.6 javac來編譯。

 

1.String literal,這裡將它翻譯為字面常量,它由雙引號包圍的0個或多個字元組成,比如"abc","Hello World"等等。一個String字面常量總是引用相同的String執行個體,比如"abc","abc"兩個常量引用的是同一個對象。

 

程式測試:

package testPackage;

class Test {

  public static void main(String[] args) {

  String hello = "Hello", lo = "lo";

  System.out.print((hello == "Hello") + " ");

  System.out.print((Other.hello == hello) + " ");

  System.out.print((other.Other.hello == hello) + " ");

  System.out.print((hello == ("Hel"+"lo")) + " ");

  System.out.print((hello == ("Hel"+lo)) + " ");

  System.out.println(hello == ("Hel"+lo).intern());

  }

}

 

class Other { static String hello = "Hello"; }

 

另一個包:

 

package other;

public class Other { static String hello = "Hello"; }

輸出:

true true true true false true

結論有六點:

1) 同一個包下,同一個類中的相同的String字面常量表示對同一個String對象的引用。

2) 同一個包下,不同的類中的相同的String字面常量表示對同一個String對象的引用。

3) 不同包下,不同類中的相同String字面常量同樣表示對同一個String對象的引用。

4) 通過常量運算式計算的String,計算在編譯時間進行,並將它作為String字面常量對待。

5) 通過串連操作得到的String(非常量運算式),串連操作是運行時進行的,會新建立對象,所以它們是不同的。

6) 顯式的對一個計算得到的String調用intern操作,得到的結果是已經存在的相同內容的String字面常量。

補充說明:

1)像這樣的問題,String str = "a"+"b"+"c"+"d";

運行這條語句會產生幾個String對象?1個。參考上面第5條,通過常量運算式得到的String 是編譯時間計算的,因此執行這句話時只有"abcd"著一個String對象存在。

常量表達是的定義可以參考java語言規範。另例:

  final String str1 = "a";

  String str2 = str1+"b";

執行第二句話會有幾個String對象產生?1個。因為str1是常量,所以str1+"b"也是常量運算式,在編譯時間計算。

  遇到這種問題時,不要說它依賴於具體的編譯器或者虛擬機器實現,因為這就是規範裡有的。一般的說,java的編譯器實現應該遵守《java語言規範》,而java虛擬機器實現應該遵守《java虛擬機器規範》。

 

2)不要這樣使用字串:

String str = new String("abc");

  參考文檔中的說明:

String

public String(String original)

  初始化一個新建立的 String 對象,使其表示一個與參數相同的字元序列;換句話說,新建立的字串是該參數字串的副本。由於 String 是不可變的,所以無需使用此構造方法,除非需要 original 的顯式副本。

參數:

original - 一個 String。

注意:無需使用此構造方法!!!

 

3)單獨的說明第6點:

String str = new String("abc");

str = str.intern();

  當調用 intern 方法時,如果池已經包含一個等於此 String 對象的字串(用 equals(Object) 方法確定),則返回池中的字串引用。否則,將此 String 對象添加到池中,並返回此 String 對象的引用。

  很明顯,在這個例子中"abc"引用的對象已經在字串池中了,再調用intern返回的是已經存在池中內容為"abc"的字元換對象的引用。在上面的例子中也說明了這個問題。

2. String類的執行個體表示表示Unicode字元序列。String字面常量是指向String執行個體的引用。(字面常量是“引用”!)

3.String轉換

  對於基本類型先轉換為參考型別;參考型別調用toString()方法得到String,如果該參考型別為null,轉換得到的字串為"null"。

4. String連結操作“+”

  如果“+”操作的結果不是編譯期常量,將會隱式建立一個新的對象。為了提高效能,具體的實現可以採用StringBuffer,StringBuilder類對多個部分進行串連,最後再轉換為String,從而避免產生再丟棄中間的String對象。為了達到共用執行個體的目的,編譯期常量總是“interned”的。

例子:

String a = "hello ";

String b = a+1+2+"world!";

反組譯碼結果:

0: ldc #2; //String hello

  2: astore_1

  3: new #3; //class java/lang/StringBuilder

  6: dup

  7: invokespecial #4; //Method java/lang/StringBuilder."<init>":()V

  10: aload_1

  11: invokevirtual #5; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

  14: iconst_1

  15: invokevirtual #6; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;

  18: iconst_2

  19: invokevirtual #6; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;

  22: ldc #7; //String world!

  24: invokevirtual #5; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

  27: invokevirtual #8; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;

  30: astore_2

 

實際就是

String b = new StringBuilder().append(a).append(1).append(2).append("world").toString();

這裡就使用StringBuilder來避免中間臨時String對象的產生而導致效能下降。

  補充例子,下面的兩個例子主要是對編譯時間常量做一個說明:

1)

String c = "c";

String str = "a"+"b"+c;

2)

String c = "c";

String str = c+"a"+"b";

1)中,str="a"+"b"+c;編譯器分析是會把"a"+"b"作為編譯時間常量,產生字面常量"ab",所以實際執行這句話時,連結的是"ab"和c。實際相當於執行了

String str = new StringBuilder().append("ab").append(c).toString();

2)中,String str = c+"a"+"b";

編譯器分析到c為變數,後面的"a"+"b"就不會作為編譯時間常量來運算了。

實際運行時相當於執行

String str = new StringBuilder().append(c).append("a").append("b").toString();

5.String對象的建立:

1) 包含String字面常量的類或者介面在載入時建立表示該字面常量的String對象。以下兩種情況下不會建立新String對象。

a) 一個相同的字面常量已經出現過。

b) 一個相同內容的字串已經調用了intern操作(比如經過運算產生的字串調用intern的情形)。

2) 非常量運算式的字串串連操作有時會產生表示結果的String對象。

3) String字面常量來自類或介面的二進位表示中(也就是class檔案中)的CONSTANT_String_info 結構。CONSTANT_String_info結構給出了構成字串字面常量的Unicode字元序列。 

4) 為了產生字串字面常量,java虛擬機器檢查 CONSTANT_String_info結構給出的字元序列:

a) 如果與CONSTANT_String_info結構中給出的字元換內容相同的串執行個體已經調用過String.intern,得到的字串字面常量就來自該串的同一執行個體。

b) 否則,根據CONSTANT_String_info 中的字元序列建立一個新的字串執行個體,然後調用intern方法。

例子:一個SCJP題目

11. public String makinStrings() {
12. String s = “Fred”;
13. s = s + “47”;
14. s = s.substring(2, 5);
15. s = s.toUpperCase();
16. return s.toString();
17. }
How many String objects will be created when this method is invoked?

答案是3個。上面已經說明,"Fred","47"是字串字面常量,它們在在類載入時建立的。這裡題目問,方法調用時(!)有多少個String對象被建立,兩個字面常量自然不包括在內。3個是:"Fred47","ed4","ED4"。

6.String與基本類型的封裝類比較

  相同點,它們都是不變類,使用"=="判斷時可能會有類似的性質。

  在java 5之後,java增加了自動裝箱和拆箱功能。因此,就有了這樣的性質:

Integer i = 5;

Integer j = 5;

System.out.println(i == j);

結果:true.

  這表面上看來是和String相同點,但其實現是極為不同的。這裡作為一個不同點來介紹。

  眾所周知,自動裝箱是這樣實現的:

Integer i = 5;

相當於

Integer i = Integer.valueOf(5);//注意不是new Integer(5),這就無法滿足java語言規範中的約定了,約定見本文最後

  而在Integer中,靜態建立了表示從-128~+127之間資料的Integer對象,這個範圍之內的數進行裝箱操作,只要返回相應的對象即可。因此

Integer i = 5;

Integer j = 5;

我們得到的是同一個對象。這是通過類庫的設計來實現的。而String的共用是通過java虛擬機器的直接支援來實現的,這是它們本質的不同。

  這是Integer類中的部分代碼:

private static class IntegerCache {

  private IntegerCache(){}

  static final Integer cache[] = new Integer[-(-128) + 127 + 1];

  static {

  for(int i = 0; i < cache.length; i++)

  cache[i] = new Integer(i - 128);

  }

 }

public static Integer valueOf(int i) {

  final int offset = 128;

  if (i >= -128 && i <= 127) { // must cache

  return IntegerCache.cache[i + offset];

  }

  return new Integer(i);

  }

關於基本類型的裝箱,Java語言規範中有如下說明:

  如果被裝箱的變數p為true,false,一個處於/u0000~/u007f之間的byte/char,或一個處於-128~+127之間的int/short,令r1和r2為對p的任何兩個裝箱操作的結果,則r1==r2總是成立的。理想的情況下,對一個基本類型變數執行裝箱操作,應該總是得到一個相同的引用。但在實踐中,在現存的技術條件下,這是不現實的。上面的規則是一個注重實效的折衷。

  最後一點,要理解java的方法調用時的傳參模型:java中只有pass by value。(不明確這一點,就有亂七八糟的解釋,比如典型的Java既有傳值,又有傳引用,String很特殊……)

//改變參數的值?

public void test(String str){

  str = "Hello";

}

//改變參數的值?

public void test(StringBuffer buffer){

  buffer = new StringBuffer("Hello");

}

//交換兩個Integer?

public void swap(Integer a,Integer b){

  Integer temp = a;

  a = b;

  b = temp;

}

這三個方法全是沒有意義的方法。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.