2016-01-22 65 views
0

請考慮以下代碼段。爲什麼使用Java引用變量的「+」運算符和文字串聯不會導致String interning?

CASE#1

public class HelloWorld { 
    public static void main(String[] args) { 
     String str1 = "abc"; 
     String str2 = "ab"; 

     str2 = str2 + "c"; 

     System.out.println("str1 :" + str1+ ", str2 :" + str2); 

     System.out.println(str1 == str2); 
    } 
} 

結果是

sh-4.3$ java -Xmx128M -Xms16M HelloWorld                                   
str1 :abc, str2 :abc                                        
false 

這裏,str1 == str2結果出來是假的。但是,如果使用「+」運算符連接兩個文字。它爲您提供字符串常量池中字符串文字「abc」的地址。考慮下面的代碼片段

CASE#2

public class HelloWorld { 
    public static void main(String[] args) { 
     String str1 = "abc"; 
     //String str2 = "ab"; 

     str2 = "ab" + "c"; 

     System.out.println("str1 :" + str1 + ", str2 :" + str2); 

     System.out.println(str1 == str2); 
    } 
} 

結果是

sh-4.3$ java -Xmx128M -Xms16M HelloWorld                                   
    str1 :abc, str2 :abc                                        
    true 

是否有人可以解釋爲什麼字符串中的實習案例#2是做,而不是在CASE#1?爲什麼我們在CASE#1中將'str1 == str2'設爲false,在CASE#2中爲true?

+1

用'javap -v'檢查字節碼;你會明白爲什麼。 –

+0

實習成本很高。默認情況下,您可能不希望將其用於非常量字符串(在第一種情況下,乍一看有一部分看起來可變 - 即沒有潛在的靜態代碼分析)。 – Thilo

回答

3

因爲JLS #3.10.5指定了字符串文字或常量字符串表達式的編譯時間實現,並且在非常量字符串表達式的情況下沒有指定任何實現。

也在JLS #15.28中指定。

1

你會注意到因爲Final,編譯器認爲這是一個常量表達式,並且在這種情況下也會實例化它。

public class HelloWorld { 
    public static void main(String []args) { 
    String str1 = "abc"; 
    final String str2 = "ab"; 

    String str3 = str2 + "c"; 

    System.out.println("str1 :" +str1+", str3 :"+str3); 

    System.out.println(str1 == str3); 
    } 
} 
+0

Just interned(在運行時仍然連接)或已由編譯器連接(字節碼已包含「abc」)? – Thilo

+0

@Thilo由編譯器完成。 '編譯器......實習生'。 – EJP

1

關鍵因素不是它是一個字符串文字,而是它是一個常量表達式。這在JLS 15.28中定義,其中列出了可以有一個常量表達式的所有方法。該列表包括「String類型的文字」以及兩個字符串常量*的連接,但不包含非final變量,即使這些變量恰好設置且始終不變。

JLS 15.28也是什麼規定,「String類型的常量表達式總是‘實習’」,所以,如果事情是不一個常量表達式 - 例如,如果包括非最終變量 - 那麼它不會被拘留。


*這被表達略微笨拙,但基本上15.28說,一個表達式是恆定的,如果它僅由一堆東西的,和其中的一件事情是該添加劑操作者+,這對於字符串進行級聯 - 實際上並沒有單獨的「concatenationi操作員」。

+0

爲什麼downvote? – yshavit

+0

關鍵因素不是常量表達式,而是[JLS#3.10.5]中的規則(https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.10 .5)說明了如何處理它。 – EJP

+0

@EJP如果你做了'final String s1 =「foo」; String s2 = s1 +「bar」; s2 ==「foobar」'那麼你會得到'true',即使s2由非文字組成。 – yshavit

0

這裏的幾個現有答案在嚴格意義上已經涵蓋了爲什麼實習沒有發生:它不是JLS所要求的。

的JLS specicies既該實習必須"literal" + "literal"情況下發生(每15.18.1,同時又有非恆定表達式必須是新創建(也在15.18.1)。在新創建的對象的相關章節似乎離開打開編譯器或運行時實習的非文字的情況下,以及可能性(引號加):

Execution of a string concatenation operator (§15.18.1) that is not part 
of a constant expression (§15.28) "sometimes" creates a new String object 
to represent the result. String concatenation operators may also create 
temporary wrapper objects for a value of a primitive type. 

在這裏,我相信「有時」創建部分是指編譯器(或運行時)可以優化單個表達式中的一系列連接操作,例如foo + 1 + anotherFoo + bar()新創建的只有一個字符串用於最終結果 - 但它不允許編譯器或運行時間來實習最終結果。

一個實際的原因是,實習所有字符串並不是一個好主意,因爲實習是一個相對昂貴的全球操作。 Interned字符串必須被記錄下來,檢查唯一性,以及所有必須在可能涉及鎖定或一些更快但仍然非鎖定的無鎖邏輯的線程中安全的字符串。實際上,在大型應用程序中,如果默認內聯表的大小沒有增加(看起來鎖在表中的每個存儲桶中發生),則String.intern()可能是一個重要的爭用點。

一旦要求是這樣寫的,它本質上是不可能的未來的規範將其鬆開,因爲現有的代碼可能容易依賴的對象是新建:區別不僅通過運營商==也當是相當明顯的鎖定的對象,使用標識哈希碼等

對於對位,考慮autoboxed整數的行爲,以及相關的靜態方法如Integer.valueOf()

This method will always cache values in the range -128 to 127, 
inclusive, and may cache other values outside of this range. 

CAC的這裏提到的興奮本質上等同於實習。在這種情況下,JLS留下了在-128至127範圍外實習整數的可能性 - 即將其留給實施。實際上,這使得開發人員使用valueOf的可預測性較低,但爲實現提供了更大的靈活性,而且實際上這種靈活性在JDK 7至increase the size of the cached integer range中使用 - 甚至可以由啓動JVM的最終用戶進行配置。

相關問題