【问题标题】:The return of String.intern() explainedString.intern() 的返回解释
【发布时间】:2019-08-03 18:44:05
【问题描述】:

考虑:

String s1 = new StringBuilder("Cattie").append(" & Doggie").toString();
System.out.println(s1.intern() == s1); // true why?
System.out.println(s1 == "Cattie & Doggie"); // true another why?

String s2 = new StringBuilder("ja").append("va").toString();
System.out.println(s2.intern() == s2); // false

String s3 = new String("Cattie & Doggie");
System.out.println(s3.intern() == s3); // false
System.out.println(s3 == "Cattie & Doggie"); // false

String.intern()返回值 让我感到困惑,为什么它们会产生不同的结果:

当调用 intern 方法时,如果池中已经包含一个与该 String 对象相等的字符串,由 equals(Object) 方法确定,则返回池中的字符串。否则,将此 String 对象添加到池中并返回对该 String 对象的引用。

尤其是在这两个测试之后:

assertFalse("new String() should create a new instance", new String("jav") == "jav");
assertFalse("new StringBuilder() should create a new instance",
    new StringBuilder("jav").toString() == "jav");

我曾经读过一篇文章,谈到一些 special strings 在其他一切之前实习,但现在它真的很模糊。

如果有一些字符串pre-interned,有没有办法获得它们的列表?我只是好奇它们会是什么。


更新

感谢@Eran 和@Slaw 的帮助,我终于可以为输出解释刚才发生的事情了

true
true
false
false
false
  1. 由于池中不存在"Cattie & Doggie",s1.intern()会将当前对象引用放到池中并返回自己,所以s1.intern() == s1;
  2. "Cattie & Doggie" 现在已经在池中,所以字符串文字 "Cattie & Doggie" 将只使用池中的引用,实际上是 s1,所以我们又得到了 true
  3. new StringBuilder().toString() 将创建一个新实例,而 "java" 已经在池中,然后在调用 s2.intern() 时将返回池中的引用,因此 s2.intern() != s2 和我们有 false;
  4. new String() 也会返回一个新实例,但是当我们尝试 s3.intern() 时,它会返回池中先前存储的引用,实际上是 s1 所以 s3.intern() != s3 和我们有 false;
  5. 正如 #2 已经讨论过的,字符串文字 "Cattie & Doggie" 将返回已经存储在池中的引用(实际上是 s1),所以 s3 != "Cattie & Doggie"false 再次出现。

感谢@Sunny 提供获取所有interned 字符串的技巧。

【问题讨论】:

    标签: java string-interning


    【解决方案1】:

    字符串字面量(那些像“字符串”一样硬编码的字面量)已经被编译器为你准备好了。但是那些以编程方式获取的字符串不是,并且只有在您使用.intern() 方法时才会被实习。

    通常您不会手动实习字符串,除非您知道您将在内存中存储大量重复的字符串,因此您可以通过这种方式节省大量内存。

    这里解释: What is Java String interning?

    【讨论】:

      【解决方案2】:

      当对 String 对象调用 intern() 方法时,它会在池中查找此 String 对象包含的字符串,如果在其中找到该字符串,则返回池中的字符串。否则,将此 String 对象添加到池中并返回对该 String 对象的引用。

      所以java 字符串必须已经在池中。因此它是假的。

      您可以打印池中的所有字符串

      How to print the whole String pool?

      如果您使用 openjdk,这里是一个 example 来获取所有字符串。

      【讨论】:

      • 我刚刚尝试使用您随附的 github 中的 example,虽然我添加了它需要 $JAVA_HOME/lib/sa-jdi.jar 的依赖项,但它似乎不起作用。至于操作系统链接,如何打印整个字符串池?,它还没有经过测试,但看起来很棘手。感谢您的帮助:)
      【解决方案3】:

      s2.intern() 将返回s2 引用的实例,前提是字符串池在调用之前不包含值为“java”的String。在执行代码之前,JDK 类会实习一些 Strings。 “java”必须是其中之一。因此,s2.intern() 返回之前的实习实例,而不是 s2

      另一方面,JDK 类没有实习过任何值等于“Cattie & Doggie”的String,所以s1.intern() 返回s1

      我不知道任何预实习字符串列表。这样的列表很可能被视为实现细节,可能会因不同的 JDK 实现和 JDK 版本而异,不应依赖。

      【讨论】:

      • 感谢您的详细解释,这样说是否正确: s.intern() 将返回原始引用,如果字符串没有被实习,但如果它已经被实习(在常量池中)那么它返回常量池中的引用?
      • @Hearen 是的,正如 javadoc 所说的那样 - “当调用实习生方法时,如果池中已经包含一个等于该 String 对象的字符串,该字符串由 equals(Object) 方法确定,那么该字符串从池中返回。否则,将此 String 对象添加到池中并返回对此 String 对象的引用。"
      • 很抱歉再次打扰您。如果是这样,那么为什么第二个为什么仍然返回true? ......现在很混乱......但是当我用new String()替换new StringBuilder().toString()时,它们都将变成false。好奇怪……
      • “任何预驻留字符串列表”将取决于您的程序也恰好加载了哪些 JDK 类。
      • @AlexeyRomanov 和启动器,例如常用的标准启动器加载指定的主类并在其上执行getMethod("main", String[].class),从而“预实习”字符串"main"。不同的启动器,例如通过 JNI 调用 main 方法的本机启动器的行为会有所不同。同样,处理命令行选项的方式可能不同,因此对“预实习”字符串列表有不同的影响。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-01-16
      • 2013-03-25
      • 2020-11-21
      • 1970-01-01
      • 2018-02-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多