【问题标题】:Strange String pool behavior奇怪的字符串池行为
【发布时间】:2013-07-08 11:13:29
【问题描述】:

我有一些奇怪的字符串池行为的问题。 我正在使用== 比较相等的字符串,以确定它们是否在池中。

public class StringPoolTest {
  public static void main(String[] args) {
    new StringPoolTest().run();
  }

  String giveLiteralString() {
    return "555";
  }

  void run() {
    String s1 = giveLiteralString() + "";
    System.out.println("555" == "555" + "");
    System.out.println(giveLiteralString() == giveLiteralString() + "");
  }
}

输出是:

true
false

这对我来说是一个很大的惊喜。有人可以解释一下吗? 我认为这在编译时正在发生。但是为什么将"" 添加到字符串中会产生任何影响呢?

【问题讨论】:

  • @MarkoTopolnik 在我看来是一样的。
  • @MarkoTopolnik 我知道问题略有不同。但答案总是像“XXX 是编译时常量,而 YYY 不是”。不过,也许我选错了问题。
  • @johnchen902 我同意,但是您将错误的问题发布为重复 :-)
  • 您真的要比较引用或返回的字符串吗?
  • @MareInfinitus 是的,为什么不呢?它比使用 equals 进行比较要快得多。当然,您必须确保所有字符串都在池中(例如,通过 intern())。

标签: java string


【解决方案1】:
"555" + ""

compile-time constant,而

giveLiteralString() + ""

不是。因此前者编译成字符串常量“555”,而后者编译成实际的方法调用和连接,产生一个新的String实例。


另见JLS §3.10.5 (String Literals)

在运行时通过连接计算的字符串是新创建的,并且 因此不同。

【讨论】:

  • 您能否在方法调用的情况下提供有关新字符串实例的引用?任何 JLS 链接?
  • 另外,在编译前/编译时运行的代码优化器可能已经将"555"+"" 组合成一个字符串对象"555",而method()+"" 在编译后仍将是method()+""。跨度>
  • @sanbhat 在我的回答中点击“编译时常数”。
  • 附带问题:如果你将final 添加到giveLiteralString(),会不会有任何改变?
  • @RandyMarsh 它不会:查看编译时常量的允许表达式列表。
【解决方案2】:

反编译此行后

System.out.println("555" == "555" + "");

我得到了这个字节码

    LINENUMBER 8 L0
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ICONST_1
    INVOKEVIRTUAL java/io/PrintStream.println(Z)V
    ...

相当于

  System.out.println(true);

这意味着表达式"555" == "555" + "" 编译为布尔值true

giveLiteralString() == giveLiteralString() + "" javac 构建了这个字节码

    LINENUMBER 8 L0
    INVOKESTATIC Test1.giveLiteralString()Ljava/lang/String;
    NEW java/lang/StringBuilder
    DUP
    INVOKESTATIC Test1.giveLiteralString()Ljava/lang/String;
    INVOKESTATIC java/lang/String.valueOf(Ljava/lang/Object;)Ljava/lang/String;
    INVOKESPECIAL java/lang/StringBuilder.<init>(Ljava/lang/String;)V
    INVOKEVIRTUAL java/lang/StringBuilder.toString()Ljava/lang/String;
    IF_ACMPNE L1
    ...

相当于

if (giveLiteralString() == new StringBuilder(giveLiteralString()).append("").toString()) {
...

这总是会产生错误,因为我们在这里比较 2 个不同的对象。

【讨论】:

  • "which will always generate false" -- 从技术上讲,StringBuilder 不是必需 来生成非内部字符串。只是它没有充分的理由尝试生产一个被拘留者。
  • 从 StringBuilder.toString API - 分配并初始化一个新的 String 对象以包含该对象当前表示的字符序列。然后返回这个字符串。
  • 但没有什么说不能实习。
  • @HotLicks 如果它是实习的,就不能保证它实际上是新的。如果字符串池中已包含该字符串,则 intern 将返回旧实例。
  • @HotLicks 我读到它是因为返回了一个新的 String 对象。那总是新的,而不是池中的对象
【解决方案3】:

在第二种情况下,编译器可以识别出+ "" 是一种无操作,因为"" 是一个已知为零长度的编译时值。但是编译器仍然需要检查来自giveLiteralString 的结果是否为空(因为在未优化的情况下,+ 操作会导致空值检查),因此最简单的方法是不尝试优化。

因此,编译器生成代码来执行连接,并创建一个新字符串。

【讨论】:

  • 编译器必须遵守JLS,明确规定运行时拼接的结果是一个新的字符串。
【解决方案4】:

编译时连接 由常量表达式计算的字符串在编译时完成并被视为常量或文字意味着字符串或表达式的值在编译时是已知的或评估的,因此编译器可以检查字符串池中的相同值并返回相同的字符串对象引用.

运行时串联 其值已知或无法在编译时评估但取决于运行时的输入或条件的字符串表达式,则编译器将不知道字符串的值,因此总是使用 StringBuilder 附加字符串并始终返回新字符串。 我想这个例子会更好地阐明它。

public static void main(String[] args) {
    new StringPoolTest().run();
  }
  String giveLiteralString() {
    return "555";
  }

  void run() {
    System.out.println("555" + 9 == "555" + 9);  
    System.out.println("555"+Integer.valueOf(9) == "555" + Integer.valueOf(9)); 
    System.out.println(giveLiteralString() == giveLiteralString());
    // The result of runtime concatenation is a fresh string.
    System.out.println(giveLiteralString() == giveLiteralString() + "");
  }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-20
    • 1970-01-01
    • 2020-06-18
    • 2015-05-29
    • 2023-03-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多