2015-11-29 237 views
2

我明白,一個方法內:字符串相等構建從字符數組字符串或串接字符

String myStr1 = "good"; 
String myStr2 = "good"; 
System.out.println(myStr1==myStr2); 

打印真。 出於同樣的原因:

String myStr1 = "good"; 
String myStr2 = ""+'g'+'o'+'o'+'d'; 
System.out.println(myStr1==myStr2); 

打印也是如此。

那麼爲什麼:

String myStr1 = "good"; 
char[] myCharArr = {'g', 'o', 'o', 'd' }; 
String myStr2 = ""+myCharArr[0]+myCharArr[1]+myCharArr[2]+myCharArr[3]; 
System.out.println(myStr1==myStr2); 

打印假的? 我沒有看到兩個最後的代碼之間的區別。 有什麼想法? 謝謝。

+1

前兩個示例在編譯時連接在一起,並解析爲同一個對象。最後一個示例在運行時連接在一起,通常會創建一個新的唯一String對象。 – Nayuki

+0

我明白了。韓國社交協會。 –

+1

http://stackoverflow.com/questions/513832/how-do-i-compare-strings-in-java是一個很好的頁面書籤爲此:) – nullpointer

回答

3

編譯器替代了多個值相等字符串期從常量表達式構建是這樣的:

String myStr1 = "good"; 
String myStr2 = ""+'g'+'o'+'o'+'d'; 
System.out.println(myStr1==myStr2); 

隨着從的String.intern獲得一個唯一的字符串對象。然後將這個唯一的String對象分配給兩個變量。這就是爲什麼他們然後參考平等。

String myStr1 = "good"; 
char[] myCharArr = {'g', 'o', 'o', 'd' }; 
String myStr2 = ""+myCharArr[0]+myCharArr[1]+myCharArr[2]+myCharArr[3]; 
System.out.println(myStr1==myStr2); 

編譯器無法對此進行優化,因爲它有一個不是常量表達式的數組引用。這會導致兩個不相等的String對象。否則將違反Java語言規範。

下面是從Java語言規範常量表達式的定義:

常量表達式是表示原始 類型或字符串的一個值,該值不會突然完成並使用 由表達式只有以下:

  • 原始類型和類型字符串的文字(§3.10.1, §3.10.2,§3.10.3,§3.10.4,§3.10.5)的字面

  • 強制轉換爲原始類型和強制轉換爲串類型(§15.16)

  • 一元運算符+, - , - 和! (但不是++或 - )(§15.15.3, §15.15.4,§15.15.5,§15.15.6)

  • 乘法運算符*,/,和%(§15.17)

  • 加法運算符+和 - (§15.18)

  • 移位運算符<<>>,和>>>(§15.19)

  • 關係運算符<<=>,一個d >=(但不是的instanceof)(§15.20)

  • 等式運算==!=(§15.21)

  • 按位和邏輯運算符&,^和| (§15.22)

  • 條件運算符& &和條件運算符|| (§15.23,§15.24)

  • 三元條件運算符? :(§15.25)

  • 括號表達式(§15.8.5)其包含的表達式是一個常量表達式。

  • 引用常量變量(§4.12.4)的簡單名稱(§6.5.6.1)。

  • 格式TypeName的合格名稱(第6.5.6.2節)。引用常量變量的標識符(§4.12.4)。

字符串類型的常量表達式總是被「interned」,以便使用方法String.intern共享唯一的實例。

常量表達式始終被視爲FP-strict(第15.4節),即使它在發生在非常量表達式不會被認爲是FP嚴格的 的上下文中時發生。

來源:http://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#d5e30892

+1

還要注意,即使myCharArray被聲明爲「final」,它仍然不是一個常量表達式。 –

+0

@KlitosKyriacou是的,非常重要。 **常量表達式**因爲我已經使用它是指在語言規範中的一個特定的和受約束的定義,並不是所有的語義常量表達式:) –

3

myCharArr[0]不能在編譯時評估,因爲編譯器(誰的聰明是限制器)認爲有可能在運行時將字符串連接起來之前,這個數組可能會被編輯(也可能是由其他線程)內容可以改變,所以它不會假設例如myCharArr[0]應該是'g'(也許將來這種行爲將會得到改善)。

因此,儘管有像""+'g'+'o'+'o'+'d'編譯器代碼確實是這樣處理的值,就可以計算出該結果字符串將"good"(因爲我們使用的編譯時間常數),從而優化我們的代碼,並阻止每次重新計算這個表達式我們運行我們的代碼,它只是用"good"代替""+'g'+'o'+'o'+'d'
但是,由於它無法評估myCharArr[0]這個表達式,所以它不能以相同的方式優化我們的代碼,這意味着它需要將該字符串的創建保留在運行時執行的代碼中。


現在,如果你想知道爲什麼==回報true"good"=="good"false代碼像"good"==new String("good")你需要知道的是:

  • ==比較引用,換句話說,它可以讓我們測試,如果我們比較參考存儲相同的對象(如果你想檢查對象是否使用equal方法)
  • Java有Stri吳池存儲文字,以避免重新創建存儲相同的數據和編譯器的許多String對象增加了負責將和從池中獲取文字代碼,所以當你"good"=="good"兩個文字是從池中其中true證實
  • 但是編譯器沒有按相同的對象不會添加負責放入池或從其中檢索的代碼字符串在運行時明確使用new Sring(data)構造函數來防止在池字符串中最有可能不再被再次處理所以使用"good"==new String("good")您正在比較池中的兩個不同對象"good",和new String(...)這是從池中分離(確認結果false==)。
+1

這是一個沒有參考常量表達式和語言規範的小波動。它不是基於數組更改的可能性。從技術上講,靜態分析可以排除這個問題,所以它確實是常量表達式的JLS定義。 –

+1

「*靜態分析可以排除這種情況,所以它確實是常量表達式的JLS定義,它在這裏很重要*」這是非常真實的。 Java編譯器已經可以很好地處理存儲在最終變量中的編譯常量的串聯,因此我們希望將來可以對其他編譯器進行改進。無論如何更新我的答案一點。我並沒有試圖根據「因爲JLS這麼說」而創建答案。我試圖專注於可能的問題/原因,這可能表明爲什麼JLS會這麼說。 – Pshemo

+0

這是有道理的。這是未來語言規範可以擴展常量表達式定義的一個領域。當它發生時,這會暴露出一些有趣的錯誤;) –

1

下面的語句

String myStr2 = ""+myCharArr[0]+myCharArr[1]+myCharArr[2]+myCharArr[3]; 

將被編譯到以下幾點:

  1. StringBuilder sb = new StringBuilder()
  2. sb.append(myCharArr[0]) ... sb.append(myCharArr[3])
  3. ,然後調用sb.toString()它返回一個新的String

反編譯的字節碼,你會看到這樣的事情

28: invokespecial #3     // Method java/lang/StringBuilder."<init>":()V 
    31: ldc   #4     // String 
    33: invokevirtual #5     // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 
    36: aload_1 
    37: iconst_0 
    38: caload 
    39: invokevirtual #6     // Method java/lang/StringBuilder.append:(C)Ljava/lang/StringBuilder; 
    42: aload_1 
    43: iconst_1 
    44: caload 
    45: invokevirtual #6     // Method java/lang/StringBuilder.append:(C)Ljava/lang/StringBuilder; 
    48: aload_1 
    49: iconst_2 
    50: caload 
    51: invokevirtual #6     // Method java/lang/StringBuilder.append:(C)Ljava/lang/StringBuilder; 
    54: aload_1 
    55: iconst_3 
    56: caload 
    57: invokevirtual #6     // Method java/lang/StringBuilder.append:(C)Ljava/lang/StringBuilder; 
    60: invokevirtual #7     // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 

當其他語句

String myStr1 = "good"; 
String myStr2 = ""+'g'+'o'+'o'+'d'; 

聲明兩個常量字符串,這裏是byte-code

0: ldc   #2     // String good 
    2: astore_1 
    3: ldc   #2     // String good 
    5: astore_2 

編譯器馬上將它們聲明爲常量。

+0

很酷的答案。你能顯示其他情況下出現的字節碼是否相等? –

+0

關於這一點,Java Language Specification中肯定會有一些東西。沒有一個明確的答案,結果將是編譯器實現依賴,這是可怕的。 –

+1

@ AlainO'Dea我剛剛編輯了我的答案,我正在尋找規範中的解釋,但到目前爲止沒有運氣:) –

1

只有編譯時常量字符串被自動實現。在Oracle documentation中描述了常量字符串(通常用於常量表達式)。通過這個定義,你的char數組不是常量,因此使用它的表達式將創建一個新的String對象。