不可变场景
String 类和 Integer 和 Double 等包装类都是不可变的。这意味着当您执行以下操作时:
1. String s1 = "a";
2. s2 = s1;
3. s1 = s1 + "b";
4. System.out.println(s1 == s2); // prints false
注意幕后真正发生的事情(非常简化,并使用虚假的内存地址):
- (第 1 行)在内存地址
0x000001 处创建一个字符串 "a"。
- (第 1 行)将
s1 的值设置为0x000001,使其有效地指向字符串"a"。
- (第 2 行)复制
s1 的值并将其设置为 s2。所以现在s1 和s2 都有相同的0x000001 值,所以都指向字符串"a"。
- (第 3 行)找到
s1 指向的内容(字符串"a"),并使用它创建一个新的、不同的"ab" 字符串,它将位于0x000002 的不同内存地址。 (注意字符串"a"在内存地址0x000001处保持不变)。
- (第 3 行)现在将值
0x000002 分配给变量 s1,以便它现在有效地指向这个新字符串 "ab"。
- (第 4 行)比较
s1 和 s2 的值,它们现在分别位于 0x000002 和 0x000001。显然,它们没有相同的值(内存地址),所以结果是 false。
- (第 4 行)将
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
注意这在内部的处理方式有多么不同(同样,非常简化,甚至省略了一些细节以保持简单易懂):
- (第 1 行)在内存地址
0x000001 处创建一个新的 StringBuffer 实例,其内部状态为 "a"。
- (第 1 行)将
sb 的值设置为 0x000001,以便它有效地指向 StringBuffer 实例,该实例本身包含 "a" 作为其状态的一部分。
- (第 2 行)复制
sb 的值并将其设置为 sb2。所以现在sb 和sb2 具有相同的0x000001 值,因此它们都指向同一个StringBuffer 实例。
- (第 3 行)找到
sb 指向的内容(StringBuffer 实例),并在其上调用 .append() 方法,要求其将其状态从 "a" 更改为 "ab"。 (非常重要!!!与不可变版本不同,sb 的内存地址确实不会改变。所以sb 和sb2 仍然指向相同的StringBuffer 实例。
- (第 4 行)比较
sb 和 sb2 的值,它们都仍为 0x000001。这一次,它们的值相同,所以结果是true。
- (第 4 行)将
true 打印到控制台。
奖金考虑:== vs. 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 行)在内存地址
0x000001 处创建字符串 "abc"。
- (第 1 行)将
s1 的值设置为0x000001,使其有效地指向字符串"abc"。
- (第 2 行)在内存地址
0x000002 处创建一个新字符串 "abc"。 (请注意,我们现在有 2 个字符串 "abc"。一个在内存地址 0x000001,另一个在 0x000002)。
- (第 2 行)将
s2 的值设置为0x000002,使其有效地指向第二个字符串"abc"。
- (第 3 行)比较
s1 和 s2 的值,它们现在分别位于 0x000001 和 0x000002。显然,它们没有相同的值(内存地址),所以结果是false。 (即使它们都指向逻辑上相同的字符串,但在内存中,它们仍然是 2 个不同的字符串!)
- (第 3 行)将
false 打印到控制台。
- (第 4 行)在变量
s1(地址0x000001)指向的字符串上调用.equals()。并作为参数传递对变量s2(地址0x000002)指向的字符串的引用。 equals 方法比较两个字符串的值,并确定它们在逻辑上相等,因此它返回true。
- (第 4 行)将
true 打印到控制台。
希望以上内容现在对您有意义。
课程呢?
== 与equals() 不同。
== 会盲目检查变量的值是否相同。在引用变量的情况下,这些值是内存地址位置。所以,即使2个变量指向逻辑上等价的对象,如果它们是内存中的不同对象,也会返回false。
equals() 用于检查 逻辑 相等性。这意味着什么完全取决于您调用的equals() 方法的具体实现。但总的来说,这是返回我们直观预期结果的方法,也是您在比较字符串时要使用的方法,以避免令人讨厌的意外惊喜。
如果您需要更多信息,我建议您进一步搜索不可变与可变类的主题。还有关于价值与参考变量的话题。
希望对你有帮助。