【问题标题】:"==" in case of String concatenation in Java"==" 在 Java 中字符串连接的情况下
【发布时间】:2021-05-16 22:50:34
【问题描述】:
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 都指向字符串常量池中的相同字符串文字。所以true 在案例 1 中

String d = "dev" + "ender";

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

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

ad 如何指向同一个引用而不是 ae

【问题讨论】:

  • 编译器在编译时知道d的一切,因此可以对其进行优化和实习。 e 使用一个变量,所以编译器在编译时并不知道它的一切。此外,即使这种行为很常见,也不能保证这种行为。
  • @AzatNugusbayev 我跟着那个教程..:P
  • @dev - case -2 没有使用StringBuilder,同样,使c final 将使case 3 返回true
  • @sidgate 绝对不是重复的。

标签: java string


【解决方案1】:

正在发生四件事:

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

  2. 同一个类中的等效字符串常量(编译器知道的字符串是根据 JLS 中的各种规则的常量)由编译器引用同一个字符串(它也在类的"constant pool" 中列出了它们) )。这就是为什么a == b 是正确的。

  3. 当类被加载时,它的每个字符串常量都自动为interned——检查JVM的字符串池是否有一个等效的字符串,如果找到,则使用String对象(如果没有,则使用新的@新常量的 987654344@ 对象被添加到池中)。因此,即使x 是在Foo 类中初始化的字符串常量,而y 是在Bar 类中初始化的字符串常量,它们也会是==

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

  4. 编译器在处理常量值时会进行字符串连接,所以

    String d = "dev" + "ender";
    

    编译成

    String d = "devender";
    

    "devender" 是一个字符串常量,编译器和JVM 将上面的第2 点和第3 点应用到。例如,没有使用StringBuilder,连接发生在编译时,而不是运行时。这在JLS§15.28 - Constant Expressions 中有介绍。所以a == d 为真,原因与a == b 为真:它们引用同一个常量字符串,因此编译器确保它们引用类常量池中的同一个字符串。

    当任何操作数不是常量时,编译器不能这样做,所以它不能这样做:

    String e = c + "ender";
    

    ...尽管代码分析很容易表明c 的值肯定是"dev",因此e 肯定是"devender"。具体来说,规范只让编译器与常量值进行连接。因此,由于编译器无法执行此操作,它会输出您引用的 StringBuilder 代码,并且该工作在运行时完成,创建一个新的 String 对象。该字符串不会自动被保留,因此e 最终会引用与a 不同的String 对象,因此a == e 是错误的。

    请注意as Vinod said,如果您将c 声明为final

    final String c = "dev";
    

    那么它将是一个constant variable(是的,它们确实被称为那个),因此 §15.28 将适用并且编译器将转向

    String e = c + "ender";
    

    进入

    String e = "devender";
    

    a == e 也是如此。

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

【讨论】:

    【解决方案2】:

    编译器在后台做了很多优化。

    String d = "dev" + "ender";
    

    这里编译器将在编译程序时将"dev" + "ender" 替换为"devender"。如果您要添加 2 个文字(这适用于原语和字符串),编译器会进行此优化。

    Java 代码:

    String d = "dev" + "ender";
    

    字节码:

      0: ldc           #16                 // String devender
    

    来个特殊情况:

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

    使c final 将使字符串成为compile-time-constant。编译器将意识到c 的值不能改变,因此在编译时会将所有出现的c 替换为值“dev”,因此e 将在编译时 自身期间解决.

    【讨论】:

    • “编译器在后台做了很多优化。”它真的没有。 Java 编译器将其留给 JVM。 JLS 要求编译器进行这种特定的替换,因为它是一个常量表达式。
    • @Boann - 编译器根据 JLS 指定的内容进行这些优化。我明白了,编译器给 JIT 留下了很多东西
    【解决方案3】:

    de 之间的区别在于,当您连接字符串literals 时,连接是在编译时执行的。 Java 编译器以与"devender" 表达式相同的方式处理"dev" + "ender" 表达式,在编译时生成相同的文字。由于所有String 字面量都被保留,d"dev" + "ender" 的结果)最终也会引用与ab"devender" 相同的对象。

    e 的表达式c + "ender"运行时 进行评估。即使它产生相同的字符串,编译器也不会使用这个事实。这就是生成不同的String 对象的原因,导致对== 的比较失败。

    【讨论】:

      【解决方案4】:

      正如你在内部所说,最后一个连接是为了类似于

      String e = new StringBuilder().append(c).append("ender").toString();
      

      StringBuildertoString() 的实现创建一个新字符串。这是实现。

      public String toString() {
           // Create a copy, don't share the array
           return new String(value, 0, count);
      }
      

      使用== 而非.equals() 比较字符串仅当两个字符串相同时才返回true。在这种情况下,它们并不相同,因为第二个字符串是作为 String 类型的新对象创建的。

      其他连接由编译器直接执行,因此不会创建新的字符串。

      【讨论】:

      • 我认为您在这里误读了这个问题。您给出的示例确实返回true,但变量ae 没有。
      • 是的,你说得对,我改了。谢谢
      【解决方案5】:

      "dev" + "ender" 是一个编译时可评估的常量表达式:两个参数都是字符串文字。因此表达式为"devender"

      c + "ender" 不能这样说:某些情况(例如在不同线程上运行的某些代码)可能导致 c 被设置为不同的值。将 c 限定为 final 可以避免这种可能性,在这种情况下,e 也将引用与 a 相同的对象。

      所以abd 都指向同一个对象。

      【讨论】:

        【解决方案6】:

        字符串 d = "dev" + "ender";常量 + 常量,‘d’仍然是常量(同一个),所以 (a == d) 为真;

        字符串 e = c + "ender";变量+常量,结果'e'是一个变量,它会在内部使用StringBuilder,并创建一个新的引用。

        【讨论】:

          【解决方案7】:

          请记住,Java 拥有一个包含程序中所有字符串字面量的池,用于匹配目的,因此上述任何不同的字符串 literal 连接将导致相同的对象,相同的对象字符串字面量。您可以查看this有用的文章了解更多信息。

          另一方面,String 对象和文字(case c + "ender")的连接将导致在运行时创建一个 as StringBuilder 对象,这与池中的文字不同。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2015-06-02
            • 2021-02-09
            • 2011-05-18
            • 2012-06-25
            • 2018-02-14
            • 1970-01-01
            相关资源
            最近更新 更多