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 

如您所见,前两个赋值(对 s1s2)加载完全相同的常量 (#2),因此使用相同的对象。而对 s4 的赋值是 而不是 定义为 constant expression (即使足够聪明的编译器可以弄清楚,也不允许这样做),因此你得到了整个 "create a StringBuilder, append the strings to it, convert the result to a new string" 过程。

有趣的是,如果在上面的代码中将 final 修饰符添加到 s3,这会使 s3 + " Computer" 再次成为常量表达式,并且两次比较都将打印 true.

毫无疑问,您已经知道,您的代码的正确性不能依赖于所有这些,但知道这一点很有趣。