为什么 Java 字符串比较在 Java 15 和 Java 11 中表现不同?

lam*_*y.x 27 java memory string equality

请考虑以下类:

class Eq {
  public static void main(String[] args) {
    System.out.println("" == ".".substring(1));
  }
}
Run Code Online (Sandbox Code Playgroud)

该示例应该显示内存中可能存在空字符串的多个副本。我仍然有一个旧的 OpenJDK 11,程序false按预期输出。在 OpenJDK 15 下,程序输出true. 为类文件生成的字节码看起来相似(即使它们的寄存器值不同):

爪哇11:

public static void main(java.lang.String[]);
  Code:
     0: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
     3: ldc           #13                 // String
     5: ldc           #15                 // String .
     7: iconst_1
     8: invokevirtual #17                 // Method java/lang/String.substring:(I)Ljava/lang/String;
    11: if_acmpne     18
    14: iconst_1
    15: goto          19
    18: iconst_0
    19: invokevirtual #23                 // Method java/io/PrintStream.println:(Z)V
    22: return
Run Code Online (Sandbox Code Playgroud)

爪哇 15:

public static void main(java.lang.String[]);
  Code:
     0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
     3: ldc           #3                  // String
     5: ldc           #4                  // String .
     7: iconst_1
     8: invokevirtual #5                  // Method java/lang/String.substring:(I)Ljava/lang/String;
    11: if_acmpne     18
    14: iconst_1
    15: goto          19
    18: iconst_0
    19: invokevirtual #6                  // Method java/io/PrintStream.println:(Z)V
    22: return
Run Code Online (Sandbox Code Playgroud)

我试图通过阅读“.”来排除静态编译器优化。来自标准输入,但这不会改变结果。我试图禁用 JIT via-Djava.compiler=NONE并尝试通过调整字符串表大小-XX:StringTableSize=100000。我现在有以下问题:

  • 有人可以重现这个问题吗(即我做对了吗?如果有帮助,我可以提供类文件)
  • 我如何找出不同行为的确切原因?
  • (在您看来)不同行为的根源是什么?

我认为只是策略来解决如何找到不回答问题的行为的原因也可能很有趣。

And*_*eas 33

这在JDK 15 发行说明 中提到。

根据 JDK-8240094 的要求对其进行了更改:

JDK-8240094:优化空子串处理

String.substring""在某些情况下返回,但可以改进以在子字符串长度为零的所有情况下都这样做。

有关的:

JDK-8240225:优化空子串处理

优化String.substring和相关操作,如stripLeading,stripTrailing避免重复创建新的空字符串。

子任务:

JDK-8251556:发行说明:优化的空子字符串处理

的实施String.substring和相关方法stripLeadingstripTrailing在此版本中已更改为避免冗余创建一个新的空字符串。这可能会影响依赖于未指定行为和空子字符串标识的代码。

  • @fluffy `""` 是默认的空字符串常量,无需为其创建常量字段,因为字符串文字始终为您保留。 (14认同)
  • @fluffy 字符串常量池是JVM在运行时合成的,合并所有常量字符串。 (8认同)
  • @fluffy 无论你使用 `""` 还是 `SomeClass.CONSTANT` 都没关系。重要的一点是,“substring”方法需要执行显式测试结果字符串的长度是否为零,并使用这些常量之一,而不是在这种情况下调用“new String(...)”。如果没有该测试,为空字符串声明常量没有帮助。 (5认同)
  • @fluffy 在字符串类中拥有公共构造函数本身就是一个历史设计错误(包括“String()”,鼓励开发人员创建冗余字符串,即使已知字符串为空)。然而,对于字符串类本身来说,这几乎没有什么区别,因为即使是“私有”构造函数也是可以访问的。一致地使用可以执行此检查的工厂方法会有所帮助,但有不同的观点。*这种优化真的是一种改进吗?*我们现在在每个“子字符串”操作中都有一个附加条件,服务于罕见的空结果的极端情况...... (5认同)
  • 只是好奇:.NET 从最早的版本开始就有“String.Empty”,Apache Commons 建议类似的最终参考“StringUtils.EMPTY”。如果从一开始就存在空字符串常量,那么这些 JDK 项目和问题可能永远不会存在。是否有向 Java 引入“默认”空字符串常量的想法? (2认同)
  • @fluffy我在我的应用程序中遇到了这样的场景,我希望我可以进一步限制“private”成员的使用,例如“该成员仅适用于方法 abc 和 xyz”。然而,在 OpenJDK 的 String 实现中,他们甚至没有尝试使用/强制执行工厂方法,在内部,构造函数被到处使用。 (2认同)