[英]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
我在這里不明白的兩點是:
s1 = s1 + "d"
輸出更改為false
。 如果我用包裝器Integer
或int
替換 String ,也會發生同樣的事情。 同樣,當我更改代碼以使用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")
,它創建一個新字符串,因此引用s1
和s2
指向不同的字符串對象。
在StringBuffer
的情況下,引用永遠不會改變。 append
更改緩沖區的內部結構,而不是對其的引用。
不可變場景
String
類和包裝類(如Integer
和Double
都是不可變的。 這意味着當您執行以下操作時:
1. String s1 = "a";
2. s2 = s1;
3. s1 = s1 + "b";
4. System.out.println(s1 == s2); // prints false
注意幕后真正發生的事情(非常簡化,並使用偽造的內存地址):
0x000001
處創建一個字符串"a"
。s1
的值設置為0x000001
,使其有效地指向字符串"a"
。s1
的值並將其設置為s2
。 所以現在s1
和s2
都有相同的值0x000001
,所以都指向字符串"a"
。s1
指向的內容(字符串"a"
),並使用它來創建一個新的不同的"ab"
字符串,該字符串將位於不同的內存地址0x000002
。 (請注意,字符串"a"
在內存地址0x000001
處保持不變)。0x000002
分配給變量s1
以便它現在有效地指向這個新字符串"ab"
。s1
和s2
的值,它們現在分別位於0x000002
和0x000001
。 顯然,它們沒有相同的值(內存地址),因此結果為false
。false
。 所以你看,當將"a"
字符串更改為"ab"
字符串時,您並沒有修改"a"
字符串。 相反,您使用"ab"
的新值創建了第二個不同的字符串,然后更改了一個引用變量以指向這個新創建的字符串。
使用其他類(如Integer
或Double
編碼時會出現完全相同的模式,這些類也是不可變的。 您必須了解,當您在這些類的實例上使用+
或-
等運算符時,您並沒有以任何方式修改實例。 相反,您正在創建一個全新的對象,並獲得對該新對象的內存地址的新引用,然后您可以將其分配給引用變量。
可變場景
這與StringBuffer
或StringBuilder
等可變類以及不幸的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
請注意內部處理的不同之處(同樣,非常簡化,甚至省略了一些細節以使其簡單易懂):
0x000001
處創建一個新的StringBuffer
實例,內部狀態為"a"
。sb
的值設置為0x000001
以便它有效地指向StringBuffer
實例,該實例本身包含"a"
作為其狀態的一部分。sb
的值並將其設置為sb2
。 所以現在sb
和sb2
都有相同的值0x000001
,所以都指向同一個StringBuffer
實例。sb
指向的內容( StringBuffer
實例),並對其調用.append()
方法以要求其將其狀態從"a"
更改為"ab"
。 (非常重要!!!與不可變版本不同, sb
的內存地址不會改變。所以sb
和sb2
仍然指向同一個StringBuffer
實例。sb
和sb2
的值,它們都仍在0x000001
。 這一次,它們都具有相同的值,因此結果為true
。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
等不可變類的更好理解,那么它實際上並不難理解,並且它教會了我們寶貴的一課。
因此,如果我們再次進行練習以檢查實際發生的情況,我們會發現以下內容:
0x000001
處創建字符串"abc"
。s1
的值設置為0x000001
,使其有效地指向字符串"abc"
。0x000002
處創建一個新字符串"abc"
。 (請注意,我們現在有 2 個字符串"abc"
。一個在內存地址0x000001
,另一個在0x000002
)。s2
的值設置為0x000002
以便它有效地指向第二個字符串"abc"
。s1
和s2
的值,它們現在分別位於0x000001
和0x000002
。 顯然,它們沒有相同的值(內存地址),因此結果為false
。 (即使它們都指向邏輯上相同的字符串,但在內存中,它們仍然是 2 個不同的字符串!)false
。.equals()
對變量s1
(地址0x000001
)指向的字符串調用.equals()
)。 並作為參數傳遞對變量s2
(地址0x000002
)指向的字符串的引用。 equals
方法比較兩個字符串的值,並確定它們在邏輯上相等,因此返回true
。true
。希望以上內容現在對您有意義。
那課呢?
==
與equals()
。
==
會盲目地檢查變量的值是否相同。 在引用變量的情況下,值是內存地址位置。 因此,即使 2 個變量指向邏輯上等效的對象,如果它們在內存中是不同的對象,它也會返回 false。
equals()
用於檢查邏輯相等性。 這意味着什么完全取決於您調用的equals()
方法的具體實現。 但總的來說,這是返回我們直觀期望的結果的方法,也是您在比較字符串時要使用的方法,以避免出現令人討厭的意外。
如果您需要更多信息,我建議您進一步搜索不可變類與可變類的主題。 還有關於值與參考變量的話題。
我希望這可以幫助你。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.