Java 中字符串连接运算符“+”的混淆
Confusion in String concatenation operator "+" in Java
据我所知,我们不能使用 ==
运算符来比较 Java 中的 String
个值。所以我写了下面的代码:
public class Test {
public static void main(String[] args) {
String s1 = "My Computer";
String s2 = "My" + " Computer";
System.out.println(s1 == s2);
}
}
我期望结果是 false
,因为这是两个不同的对象,分配了不同的内存位置(如果我错了,请在这方面纠正我)。但是当我执行代码时,输出是 true
.
然后我把s2
的值改成:
String str = "My";
String s2 = str + " Computer"; //instead of "My" + " Computer"
然后当我执行代码时,输出是 false
。
虽然我在这两个语句中都使用了+
(不是concat()
方法),但现在我无法理解这两个语句的区别。谁能解释一下。
Java 为 String 对象使用一个池 - 它试图变得聪明。这意味着当编译器可以计算出你实际上拥有相同的对象时,即使是两个看似不同的对象上的 ==
return true.
不过,如果您想比较内容,应该避免通过 ==
比较对象,因为这只会比较对象引用。对于内容比较,应使用 equals
。
你的测试用例中有一个愚蠢的错误。
String s2 = s1 + " Computer";
会分配 s2
字符串“My Computer Computer”,而不是 "My Computer".
有关如何进行字符串比较 Java,请访问 this link。
Why String is immutable in Java - 一篇文章解释了为什么无法修改 Java 中的字符串 class 的实例。为了清楚起见,请阅读此内容。
让你绊倒的是this part of the specification:
The String object is newly created (§12.5) unless the expression is a
constant expression (§15.28).
因此,当您将一个字符串常量连接到另一个字符串常量时,它算作一个常量表达式,因此将在编译时计算并替换为字符串常量 "My Computer".
你可以通过 运行 javap -c
在已编译的 class 上验证这一点。
public class Test {
public static void main(String[] args) {
String s1 = "My Computer";
String s2 = "My" + " Computer";
String s3 = "My";
String s4 = s3 + " Computer";
System.out.println(s1 == s2); //true
System.out.println(s1 == s4); //false
}
}
编译为:
public static void main(java.lang.String[]);
Code:
// s1 = "My Computer"
0: ldc #2 // String My Computer
2: astore_1
// s2 = "My" + " Computer"
3: ldc #2 // String My Computer
5: astore_2
// s3 = "My"
6: ldc #3 // String My
8: astore_3
// s4 = s3 + " Computer"
9: new #4 // class java/lang/StringBuilder
12: dup
13: invokespecial #5 // Method java/lang/StringBuilder."<
init>":()V
16: aload_3
17: invokevirtual #6 // Method java/lang/StringBuilder.ap
pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;
20: ldc #7 // String Computer
22: invokevirtual #6 // Method java/lang/StringBuilder.ap
pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;
25: invokevirtual #8 // Method java/lang/StringBuilder.to
String:()Ljava/lang/String;
28: astore 4
... the rest of the code omitted
如您所见,前两个赋值(对 s1
和 s2
)加载完全相同的常量 (#2
),因此使用相同的对象。而对 s4
的赋值是 而不是 定义为 constant expression (即使足够聪明的编译器可以弄清楚,也不允许这样做),因此你得到了整个 "create a StringBuilder, append the strings to it, convert the result to a new string" 过程。
有趣的是,如果在上面的代码中将 final
修饰符添加到 s3
,这会使 s3 + " Computer"
再次成为常量表达式,并且两次比较都将打印 true
.
毫无疑问,您已经知道,您的代码的正确性不能依赖于所有这些,但知道这一点很有趣。
据我所知,我们不能使用 ==
运算符来比较 Java 中的 String
个值。所以我写了下面的代码:
public class Test {
public static void main(String[] args) {
String s1 = "My Computer";
String s2 = "My" + " Computer";
System.out.println(s1 == s2);
}
}
我期望结果是 false
,因为这是两个不同的对象,分配了不同的内存位置(如果我错了,请在这方面纠正我)。但是当我执行代码时,输出是 true
.
然后我把s2
的值改成:
String str = "My";
String s2 = str + " Computer"; //instead of "My" + " Computer"
然后当我执行代码时,输出是 false
。
虽然我在这两个语句中都使用了+
(不是concat()
方法),但现在我无法理解这两个语句的区别。谁能解释一下。
Java 为 String 对象使用一个池 - 它试图变得聪明。这意味着当编译器可以计算出你实际上拥有相同的对象时,即使是两个看似不同的对象上的 ==
return true.
不过,如果您想比较内容,应该避免通过 ==
比较对象,因为这只会比较对象引用。对于内容比较,应使用 equals
。
你的测试用例中有一个愚蠢的错误。
String s2 = s1 + " Computer";
会分配 s2
字符串“My Computer Computer”,而不是 "My Computer".
有关如何进行字符串比较 Java,请访问 this link。
Why String is immutable in Java - 一篇文章解释了为什么无法修改 Java 中的字符串 class 的实例。为了清楚起见,请阅读此内容。
让你绊倒的是this part of the specification:
The String object is newly created (§12.5) unless the expression is a constant expression (§15.28).
因此,当您将一个字符串常量连接到另一个字符串常量时,它算作一个常量表达式,因此将在编译时计算并替换为字符串常量 "My Computer".
你可以通过 运行 javap -c
在已编译的 class 上验证这一点。
public class Test {
public static void main(String[] args) {
String s1 = "My Computer";
String s2 = "My" + " Computer";
String s3 = "My";
String s4 = s3 + " Computer";
System.out.println(s1 == s2); //true
System.out.println(s1 == s4); //false
}
}
编译为:
public static void main(java.lang.String[]);
Code:
// s1 = "My Computer"
0: ldc #2 // String My Computer
2: astore_1
// s2 = "My" + " Computer"
3: ldc #2 // String My Computer
5: astore_2
// s3 = "My"
6: ldc #3 // String My
8: astore_3
// s4 = s3 + " Computer"
9: new #4 // class java/lang/StringBuilder
12: dup
13: invokespecial #5 // Method java/lang/StringBuilder."<
init>":()V
16: aload_3
17: invokevirtual #6 // Method java/lang/StringBuilder.ap
pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;
20: ldc #7 // String Computer
22: invokevirtual #6 // Method java/lang/StringBuilder.ap
pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;
25: invokevirtual #8 // Method java/lang/StringBuilder.to
String:()Ljava/lang/String;
28: astore 4
... the rest of the code omitted
如您所见,前两个赋值(对 s1
和 s2
)加载完全相同的常量 (#2
),因此使用相同的对象。而对 s4
的赋值是 而不是 定义为 constant expression (即使足够聪明的编译器可以弄清楚,也不允许这样做),因此你得到了整个 "create a StringBuilder, append the strings to it, convert the result to a new string" 过程。
有趣的是,如果在上面的代码中将 final
修饰符添加到 s3
,这会使 s3 + " Computer"
再次成为常量表达式,并且两次比较都将打印 true
.
毫无疑问,您已经知道,您的代码的正确性不能依赖于所有这些,但知道这一点很有趣。