簡體   English   中英

字符串對象是不可變的,但引用變量是可變的。 這意味着什么?

[英]String object is immutable but reference variable is mutable. What does that mean?

我正在學習 Kathy Sierra Java 的書。 我遇到了一個這樣的問題:

public class A {
    public static void main(String args[]){
        String s1 = "a";
        String s2 = s1;
        //s1=s1+"d";
        System.out.println(s1==s2);
    }
}

輸出: true

我在這里不明白的兩點是:

  1. 當我取消注釋s1 = s1 + "d"輸出更改為false 如果我用包裝器Integerint替換 String ,也會發生同樣的事情。
  2. 同樣,當我更改代碼以使用StringBuffer

     StringBuffer sb = new StringBuffer("a"); StringBuffer sb2 = sb; //sb.append("c"); System.out.println(sb == sb2);

    現在輸出不會改變,即即使我取消對sb.append語句的注釋,它仍然是true

我無法理解這種奇怪的行為。 有人能解釋一下嗎。

在第一種情況下, s2是對s1的引用。 在第二種情況下, +被轉換為s1.concat("d") ,它創建一個新字符串,因此引用s1s2指向不同的字符串對象。

StringBuffer的情況下,引用永遠不會改變。 append更改緩沖區的內部結構,而不是對其的引用。

不可變場景

String類和包裝類(如IntegerDouble都是不可變的 這意味着當您執行以下操作時:

1. String s1 = "a";
2. s2 = s1;
3. s1 = s1 + "b";
4. System.out.println(s1 == s2); // prints false

注意幕后真正發生的事情(非常簡化,並使用偽造的內存地址):

  1. (第 1 行)在內存地址0x000001處創建一個字符串"a"
  2. (第 1 行)將s1的值設置為0x000001 ,使其有效地指向字符串"a"
  3. (第 2 行)復制s1的值並將其設置為s2 所以現在s1s2都有相同的值0x000001 ,所以都指向字符串"a"
  4. (第 3 行)找到s1指向的內容(字符串"a" ),並使用它來創建一個新的不同的"ab"字符串,該字符串將位於不同的內存地址0x000002 (請注意,字符串"a"在內存地址0x000001處保持不變)。
  5. (第 3 行)現在將值0x000002分配給變量s1以便它現在有效地指向這個新字符串"ab"
  6. (第 4 行)比較s1s2的值,它們現在分別位於0x0000020x000001 顯然,它們沒有相同的值(內存地址),因此結果為false
  7. (第 4 行)向控制台打印false

所以你看,當將"a"字符串更改為"ab"字符串時,您並沒有修改"a"字符串。 相反,您使用"ab"的新值創建了第二個不同的字符串,然后更改了一個引用變量以指向這個新創建的字符串。

使用其他類(如IntegerDouble編碼時會出現完全相同的模式,這些類也是不可變的。 您必須了解,當您在這些類的實例上使用+-等運算符時,您並沒有以任何方式修改實例。 相反,您正在創建一個全新的對象,並獲得對該新對象的內存地址的新引用,然后您可以將其分配給引用變量。

可變場景

這與StringBufferStringBuilder可變類以及不幸的java.util.Date等其他類完全相反。 (順便說一句,你最好養成使用StringBuilder而不是StringBuffer的習慣,除非你是為了多線程需求而故意使用它)

對於可變類,這些類的公開方法確實會改變(或變異)對象的內部狀態,而不是創建一個全新的對象。 因此,如果您有多個變量指向同一個可變對象,如果其中一個變量用於訪問該對象並對其進行更改,則從任何其他變量訪問同一個對象也會看到更改。

因此,如果我們采用此代碼,例如(同樣,請改用StringBuilder ,最終結果將相同):

1. StringBuffer sb = new StringBuffer("a"); 
2. StringBuffer sb2 = sb;
3. sb.append("b");
4. System.out.println(sb == sb2); // prints true

請注意內部處理的不同之處(同樣,非常簡化,甚至省略了一些細節以使其簡單易懂):

  1. (第 1 行)在內存地址0x000001處創建一個新的StringBuffer實例,內部狀態為"a"
  2. (第 1 行)將sb的值設置為0x000001以便它有效地指向StringBuffer實例,該實例本身包含"a"作為其狀態的一部分。
  3. (第 2 行)復制sb的值並將其設置為sb2 所以現在sbsb2都有相同的值0x000001 ,所以都指向同一個StringBuffer實例。
  4. (第 3 行)找到sb指向的內容( StringBuffer實例),並對其調用.append()方法以要求其將其狀態從"a"更改為"ab" 非常重要!!!與不可變版本不同, sb的內存地址不會改變。所以sbsb2仍然指向同一個StringBuffer實例。
  5. (第 4 行)比較sbsb2的值,它們都仍在0x000001 這一次,它們都具有相同的值,因此結果為true
  6. (第 4 行)向控制台打印true

額外考慮: == equals()

一旦你理解了上述內容,那么你現在就擁有了更好地理解這個特殊場景所需的知識:

1. String s1 = "abc";
2. String s2 = new String(s1);
3. System.out.println(s1 == s2); // prints false?!?
4. System.out.println(s1.equals(s2)); // prints true

令人驚訝的是,第 3 行返回false (?!?)。 但是,一旦我們了解==運算符所比較的內容,再加上對String等不可變類的更好理解,那么它實際上並不難理解,並且它教會了我們寶貴的一課。

因此,如果我們再次進行練習以檢查實際發生的情況,我們會發現以下內容:

  1. (第 1 行)在內存地址0x000001處創建字符串"abc"
  2. (第 1 行)將s1的值設置為0x000001 ,使其有效地指向字符串"abc"
  3. (第 2 行)在內存地址0x000002處創建一個新字符串"abc" (請注意,我們現在有 2 個字符串"abc" 。一個在內存地址0x000001 ,另一個在0x000002 )。
  4. (第 2 行)將s2的值設置為0x000002以便它有效地指向第二個字符串"abc"
  5. (第 3 行)比較s1s2的值,它們現在分別位於0x0000010x000002 顯然,它們沒有相同的值(內存地址),因此結果為false (即使它們都指向邏輯上相同的字符串,但在內存中,它們仍然是 2 個不同的字符串!)
  6. (第 3 行)向控制台打印false
  7. (第 4 行.equals()對變量s1 (地址0x000001 )指向的字符串調用.equals() )。 並作為參數傳遞對變量s2 (地址0x000002 )指向的字符串的引用。 equals方法比較兩個字符串的值,並確定它們在邏輯上相等,因此返回true
  8. (第 4 行)向控制台打印true

希望以上內容現在對您有意義。

那課呢?

==equals()

==會盲目地檢查變量的值是否相同。 在引用變量的情況下,值是內存地址位置。 因此,即使 2 個變量指向邏輯上等效的對象,如果它們在內存中是不同的對象,它也會返回 false。

equals()用於檢查邏輯相等性。 這意味着什么完全取決於您調用的equals()方法的具體實現。 但總的來說,這是返回我們直觀期望的結果的方法,也是您在比較字符串時要使用的方法,以避免出現令人討厭的意外。

如果您需要更多信息,我建議您進一步搜索不可變類與可變類的主題。 還有關於值與參考變量的話題。

我希望這可以幫助你。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM