String.intern() 的返回值解释

20

考虑:

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");

我曾经读过一篇帖子,讲了一些在其他所有内容之前被国际化的 特殊字符串,但现在有点模糊了。

如果有一些 预先国际化 的字符串,是否有办法获取它们的列表?我只是好奇它们可能是什么。


更新

多亏了@Eran和@Slaw的帮助,现在我终于能够解释刚才发生的事情了。

true
true
false
false
false
  1. s1.intern()将当前对象的引用放入池中并返回自己,因为"Cattie & Doggie"不存在于池中,所以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字符串的技巧。

3个回答

29

s2.intern()会返回s2所指向的实例,前提是String池中在该调用之前没有包含一个值为"java"的String。在您的代码执行之前,JDK类会对一些String进行intern操作,其中"java"必须是其中之一。因此,s2.intern()返回先前intern的实例而不是s2

另一方面,JDK类没有对任何值等于"Cattie & Doggie"的String进行intern操作,所以s1.intern()返回s1

我不知道有没有预先intern的String列表。这样的列表很可能被视为实现细节,在不同的JDK实现和版本上可能会有所不同,并且不应该依赖它。


@Hearen s1.intern() == s1 已经成立(因为s1.intern()s1引用的实例添加到了池中),所以没有必要将s1.intern()赋值给s1 - Eran
让我们在聊天中继续这个讨论 - Hearen
@Slaw 哈!我现在理解你的意思了,我将更新问题并为其绘制摘要。谢谢Slaw!谢谢Eran。 - Hearen
2
任何预先放置的字符串列表都取决于您的程序加载了哪些JDK类。 - Alexey Romanov
2
@AlexeyRomanov 和启动器,例如常用的标准启动器会加载指定的主类并对其执行 getMethod("main", String[].class) ,因此“预先内部化”字符串 "main" 。不同的启动器,例如通过 JNI 调用主方法的本地启动器将会有不同的行为。同样,命令行选项的处理方式也可能不同,因此对“预先内部化”的字符串列表产生不同的影响。 - Holger
显示剩余8条评论

3

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

所以java字符串必须已经存在于池中。因此它返回false。

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

如何打印整个字符串池?

以下是一个示例,可以获取所有字符串,如果您使用的是openjdk


我刚刚尝试了你提供的Github上的example,但似乎并没有起作用,尽管我已经添加了它所需的依赖项$JAVA_HOME/lib/sa-jdi.jar。至于操作系统链接中的如何打印整个字符串池?,尚未测试,但看起来非常棘手。感谢您的帮助 :) - Hearen

0

字符串字面量(例如“a string”这样硬编码的字符串)已经被编译器为您进行了内部化。但是那些通过编程获取的字符串没有被内部化,只有在使用.intern()方法时才会被内部化。

通常情况下,您不需要手动内部化字符串,除非您知道将在内存中存储大量重复的字符串,这样可以节省大量内存。

这里解释了这个问题:什么是Java字符串内部化?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接