Java 中的字符串串联情况下的 “==”

2022-08-31 17:47:06
String a = "devender";
String b = "devender";
String c = "dev";
String d = "dev" + "ender";
String e = c + "ender";

System.out.println(a == b);     //case 1: o/p true

System.out.println(a == d);     //case 2: o/p true

System.out.println(a == e);     //case 3: o/p false

ab 都指向字符串常量池中的同一字符串 Literal。所以在案例1中true

String d = "dev" + "ender";

应该在内部使用类似的东西:

String d = new StringBuilder().append("dev").append("ender").toString();

a & d 如何指向相同的引用,而不是 a & e


答案 1

有四件事正在发生:

  1. (您清楚地知道这一点,但对于潜伏者)测试以查看变量是否指向相同的 String 对象,而不是等效的字符串。所以即使 is 和 is 也是 ,也可能是 true 或 false,这取决于和引用的对象是同一个对象还是不同的对象。这就是为什么我们使用等价物而不是 来比较字符串的等价性。以下所有内容只是为了解释为什么有时是正确的,这不是用于比较字符串的建议。:-)==x"foo"y"foo"x == yxyString======

  2. 同一类中的等效字符串常量(编译器根据 JLS 中的各种规则知道的字符串是常量)由编译器用于引用同一字符串(编译器也在类的“常量池”中列出它们)。这就是为什么这是真的。a == b

  3. 当类被加载时,它的每个字符串常量都会被自动暂存 — JVM 的字符串池中是否有等效的字符串,如果找到一个,则使用该对象(如果没有,则将新常量的新对象添加到池中)。因此,即使 是在类中初始化的字符串常量,并且是在类中初始化的字符串常量,它们也会彼此相加。StringStringxFooyBar==

    JLS§3.10.5部分涵盖了上述第2点和第3点。(关于类常量池的一点是一个实现细节,因此前面链接到JVM规范;JLS只是说实习生。

  4. 如果编译器处理的是常量值,则编译器会执行字符串串联,因此

    String d = "dev" + "ender";
    

    被编译为

    String d = "devender";
    

    并且是编译器和 JVM 应用上述第 2 点和第 3 点的字符串常量。例如,不使用,串联发生在编译时,而不是运行时。JLS§15.28 - 常量表达式中对此进行了介绍。因此,出于同样的原因,也是如此:它们引用相同的常量字符串,因此编译器确保它们引用的是类的常量池中的相同字符串。"devender"StringBuildera == da == b

    当任何操作数不是常量时,编译器都不能这样做,因此它不能使用以下命令执行此操作:

    String e = c + "ender";
    

    ...即使代码分析可以很容易地表明,的值肯定会是,因此肯定会是 。该规范仅让编译器使用常量值进行串联,特别是。因此,由于编译器无法做到这一点,因此它会输出您引用的代码,并且该工作在运行时完成,从而创建一个新对象。该字符串不会自动暂存,因此最终引用的对象与实际对象不同,因此为 false。c"dev"e"devender"StringBuilderStringeStringaa == e

    请注意,正如Vinod所说,如果您声明为:cfinal

    final String c = "dev";
    

    然后它将是一个常量变量(是的,它们真的被称为常量变量),因此§15.28将适用,编译器将转动

    String e = c + "ender";
    

    String e = "devender";
    

    也是真的。a == e

只是重申一下:这并不意味着我们应该用来比较字符串的等价性。:-)这就是目的。==equals


答案 2

编译器在引擎盖下做了很多优化。

String d = "dev" + "ender";

在这里,编译器将替换为编译程序时。如果要添加 2 个文本(这既适用于基元也适用于字符串),编译器将执行此优化。"dev" + "ender""devender"

Java 代码 :

String d = "dev" + "ender";

字节码 :

  0: ldc           #16                 // String devender

来到一个特殊情况:

final String c = "dev"; // mark this as final
String e = c + "ender";

最终将使字符串成为编译时常量。编译器将意识到 的值不能更改,因此在编译时会将所有出现的值替换为值“dev”,因此将在编译时自行解决。ccce