为什么不能将字符串“ java”添加到“字符串池”

时间:2018-08-01 05:59:22

标签: java java-8

以下代码使我感到困惑,有人能解释为什么两个测试的行为不同吗?为什么第一个测试中的String比较返回false,而第二个测试中的String比较返回true

 public class Student {

/**
 *  Why the string "java" doesn't added to the 'String Pool' by intern() method ?
 */
@Test
public void test1() {
    String str1 = new String("ja") + new String("va");
    str1.intern();
    String str2 = "java";
    // Result:false
    System.out.println("Result:" + (str1 == str2));
}

/**
 *  Any other strings will be added to 'String Pool' as expected after intern() is invoked.
 */
@Test
public void test2() {
    String str1 = new String("ja1") + new String("va");
    str1.intern();
    String str2 = "ja1va";
    // Result:true
    System.out.println("Result:" + (str1 == str2));
}

2 个答案:

答案 0 :(得分:16)

您基本上是在检查字符串池中是否已经有一个字符串。字符串“ java”不会通过在您的第一段代码中调用intern来添加到池中,因为它已经在字符串池中。在每种方法中,您的代码:

  • 创建一个新字符串
  • 对新创建的字符串调用intern(但是忽略结果;几乎总是一个坏主意,并且您可以使用返回值轻松地检测到字符串池中先前值的存在)
  • 将新字符串与字符串文字进行比较,这将始终使用字符串池中现在的结果

现在,对intern的调用会将目标字符串添加到池中(如果目标字符串不存在的话),因此当且仅当新字符串值不是 以前在字符串池中。这等效于测试intern是否返回对调用目标的不同引用。

对于任何给定的字符串引用,有三种可能性:

  • 该精确引用已经存在于字符串池中。 (在您的代码中情况并非如此,因为您正在创建新的字符串。)
  • 在字符串池中存在对相等字符串的引用。在这种情况下,intern()将返回现有引用。
  • 字符串池中没有相等的字符串。在这种情况下,调用的目标将添加到字符串池中,并返回相同的引用。

您看到的是其他代码将内容放入字符串池的结果-很可能是加载类的一部分。下面是一个示例来证明这一点:

public class Test {
    public static void main(String... args) {
        checkInterned("ja", "va");
        checkInterned("ja", "va.lang");
        checkInterned("ja", "va.other");
        checkInterned("Int", "eger");
        checkInterned("abc", "def");
        checkInterned("Te", "st");
        checkInterned("Te", "st2");
        checkInterned("check", "Interned");
        checkInterned("check", "Interned2");
    }

    public static void checkInterned(String start, String end) {
        String x = start + end;
        String y = x.intern();
        System.out.println(x + " was interned already? " + (x != y));
    }
}

输出:

java was interned already? true
java.lang was interned already? true
java.other was interned already? false
Integer was interned already? true
abcdef was interned already? false
Test was interned already? true
Test2 was interned already? false
checkInterned was interned already? true
checkInterned2 was interned already? false

所以内插值是:

java
java.lang
Integer
Test
checkInterned

它们都是在加载类(包括正在运行的类)时自然会出现的名称。

我怀疑“ java”在这里只是一种特殊情况,因为JRE中很可能有很多条代码,用于检查字符串是否以“ java”作为保留名开头。 / p>

尽管这并不表示“ java”是关键字,它只是“字符串池中已存在的字符串”。您无需对其进行任何不同的处理。

答案 1 :(得分:9)

首先要意识到的是str1.intern()不会更改str1引用。它返回被引用的参考。因此,如果您希望str1现在成为该参考,则必须这样做:

str1 = str1.intern();

那么,为什么会有区别呢?简而言之,由于各种内部因素,JVM在其线程池中已经有一个字符串"java"

在第一个示例中,str1以新实例化的String开头(据我认为您了解)。然后,您调用str1.intern(),它返回预先存在的字符串“ java”的内部引用,但是您对该引用不做任何事情。然后,当您比较str1 == "java"时,您正在将对新实例化的对象的引用与对被隔离对象的引用进行比较,并得到false。

在第二个示例中,"ja1va"在开始的字符串池中不存在。当您调用str1.intern()时,该方法将“ ja1va”放入池中,并将其当前引用(即str1)作为规范引用。当您随后引用"ja1va"文字字符串时,JVM会查看它是否已在池中,看到它并在使用它。这样,您就可以实现。

换句话说,在第一种情况下,您要创建一个新的String对象,然后 not 实际获取其内部等效对象。在第二种情况下,您将创建一个新的String对象,将其定义为内部引用,然后通过字符串文字重新加载它。