我正在学习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);
现在输出没有变化,即使我取消注释true
语句,它仍然是sb.append
。
我无法理解这种奇怪的行为。有人可以解释一下。
答案 0 :(得分:3)
s2
是第一种情况下对s1
的引用。在第二种情况下,+
被翻译为s1.concat("d")
,这会创建一个新字符串,因此引用s1
和s2
指向不同的字符串对象。
在StringBuffer
的情况下,引用永远不会改变。 append
更改缓冲区的内部结构,而不是对它的引用。
答案 1 :(得分:2)
不可变场景
String
类以及Integer
和Double
等包装类都是不可变。这意味着当您执行以下操作时:
1. String s1 = "a";
2. s2 = s1;
3. s1 = s1 + "b";
4. System.out.println(s1 == s2); // prints false
注意幕后真正发生的事情(非常简化,并使用虚假的内存地址):
"a"
处创建字符串0x000001
。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
请注意内部处理的不同之处(再次,非常简化,甚至省略一些细节以保持简单易懂):
StringBuffer
处创建一个新的0x000001
实例,内部状态为"a"
。sb
的值设置为0x000001
,以便它有效地指向StringBuffer
实例,该实例本身包含"a"
作为其状态的一部分。 sb
的值并将其设置为sb2
。因此,现在sb
和sb2
都具有相同的0x000001
值,因此两者都指向同一个StringBuffer
实例。sb
指向的内容(StringBuffer
实例),并在其上调用.append()
方法,要求其从{{1}变更其状态到"a"
。 (非常重要!!! 与不可变版本不同,"ab"
的内存地址 NOT 更改。所以sb
和{{1}仍然指向同一个sb
实例。sb2
和StringBuffer
的值,它们都在sb
。这次,它们都具有相同的值,因此结果为sb2
。0x000001
打印到控制台。 奖金考虑因素:true
与true
一旦你理解了上述内容,那么你现在拥有了所需的知识,可以更好地理解这种奇特的场景:
==
令人惊讶的是,第3行返回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
运算符的比较结果,再加上对false
等不可变类的更好理解,那么它实际上并不难理解,它教会了我们一个有价值的教训。
因此,如果我们再次检查实际情况,我们会发现以下内容:
==
创建字符串String
。"abc"
的值设置为0x000001
,使其有效地指向字符串s1
。0x000001
处创建一个新字符串"abc"
。 (请注意,我们现在有2个字符串"abc"
。一个位于内存地址0x000002
,另一个位于"abc"
。)0x000001
的值设置为0x000002
,以便它有效地指向第二个字符串s2
。0x000002
和"abc"
的值,它们现在分别位于s1
和s2
。显然,它们没有相同的值(内存地址),因此结果为0x000001
。 (即使它们都指向逻辑上相同的字符串,在内存中,它们仍然是2个不同的字符串!)0x000002
打印到控制台。false
指向的字符串(地址false
)上调用.equals()
。并且作为参数,传递对变量s1
指向的字符串的引用(地址0x000001
)。 s2
方法比较两个字符串的值,并确定它们逻辑相等,因此返回0x000002
。equals
打印到控制台。希望以上情况对您有意义。
还有教训吗?
true
与true
不同。
==
会盲目检查变量'价值是一样的。在引用变量的情况下,值是存储器地址位置。因此,即使2个变量指向逻辑上等效的对象,如果它们是内存中的不同对象,它也将返回false。
equals()
用于检查逻辑是否相等。这意味着什么取决于您调用的==
方法的具体实现。但总的来说,这是一个能够直观地返回我们期望的结果的结果,并且是您在比较字符串时要使用的结果,以避免令人讨厌的意外惊喜。
如果您需要更多信息,我建议您进一步搜索不可变vs可变类的主题。还有关于价值与参考变量的话题。
我希望这会对你有所帮助。