intern()在Java 6和Java 7中表现不同

时间:2011-08-15 13:13:39

标签: java string java-7 java-6 string-interning

class Test {
    public static void main(String...args) {
        String s1 = "Good";
        s1 = s1 + "morning";
        System.out.println(s1.intern());
        String s2 = "Goodmorning";
        if (s1 == s2) {
            System.out.println("both are equal");
        }
    }
}

此代码在Java 6和Java 7中生成不同的输出。 在Java 6中,s1==s2条件返回false,而在Java 7中,s1==s2返回true。为什么呢?

为什么这个程序在Java 6和Java 7中产生不同的输出?

9 个答案:

答案 0 :(得分:26)

似乎JDK7过程实习生的方式与以前不同。
我用build 1.7.0-b147对它进行了测试并得到“两者都相等”,但是当用1,6.0_24执行它(相同的字节码)时,我没有收到消息。
它还取决于String b2 =...行在源代码中的位置。以下代码也不输出消息:

class Test {
   public static void main(String... args) {
      String s1 = "Good";
      s1 = s1 + "morning";

      String s2 = "Goodmorning";
      System.out.println(s1.intern());  //just changed here s1.intern() and the if condition runs true   

      if(s1 == s2) {
         System.out.println("both are equal");
      } //now it works.
   }
}

在没有在其字符串池中找到String之后看起来像intern,将实际的实例s1插入到池中。创建s2时,JVM正在使用该池,因此它获得与s1相同的引用。另一方面,如果首先创建s2,则该引用将存储到池中 这可能是因为将内部字符串从Java堆的永久生成中移出。

在此处找到:Important RFEs Addressed in JDK 7

  

在JDK 7中,实例化的字符串不再分配在Java堆的永久生成中,而是分配在Java堆的主要部分(称为年轻和旧的代),以及创建的其他对象通过申请。此更改将导致更多数据驻留在主Java堆中,并且永久生成中的数据更少,因此可能需要调整堆大小。由于此更改,大多数应用程序只会看到堆使用中的相对较小的差异,但是加载许多类或大量使用String.intern()方法的较大应用程序将看到更显着的差异。

不确定这是不是一个错误,哪个版本...... JLS 3.10.5声明

  

显式实现计算字符串的结果与具有相同内容的任何预先存在的文字字符串相同。

所以问题是预先存在的解释,编译时或执行时间:是否已经存在“Goodmorning”? 我更喜欢在7 ...之前实施WAS的方式。

答案 1 :(得分:24)

让我们从示例中省略不必要的细节:

class Test {
    public static void main(String... args) {
        String s1 = "Good";
        s1 = s1 + "morning";
        System.out.println(s1 == s1.intern()); // Prints true for jdk7, false - for jdk6.
    }
}

我们将String#intern视为黑匣子。根据运行的几个测试用例,我会得出结论,实现如下:

Java 6:
如果池包含对象等于this,则返回对该对象的引用, else创建新字符串(等于this),放入池中,并返回对该创建实例的引用。

Java 7:
如果池包含对象等于this,则返回对该对象的引用, 否则将this放入池中,然后返回this

Java 6和Java 7都没有破坏contract of the method

新的实习生方法行为似乎是修复此错误的结果:http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6962931

答案 2 :(得分:9)

==比较参考文献。实习方法确保具有相同值的字符串具有相同的引用。

String.intern method的javadoc解释了:

  

public String intern()

     

返回字符串对象的规范表示。

     

一个字符串池,最初是空的,由私有维护   class String。

     

当调用实习方法时,如果池已包含   字符串等于此字符串对象由equals(Object)确定   方法,然后返回池中的字符串。否则,这个   String对象被添加到池中以及对此String的引用   返回对象。

     

对于任何两个字符串s和t,s.intern()==   当且仅当s.equals(t)为真时,t.intern()才为真。

     

所有文字字符串和字符串值常量表达式都是   实习。字符串文字在Java语言的§3.10.5中定义   说明书

     

返回:与此字符串具有相同内容的字符串,但是    保证来自一串独特的字符串。

因此,如果没有实习,编译器会查看java代码中的常量并从中构建其常量池。 String类维护了一个不同的池,interning检查传入池中的字符串并确保引用是唯一的(这样==将起作用)。

答案 3 :(得分:7)

在jdk6中: String s1="Good";在常量池中创建一个String对象“Good”。

s1=s1+"morning";在常量池中创建另一个String对象“morning”,但这次实际上是JVM:s1=new StringBuffer().append(s1).append("morning").toString();

现在new运算符在堆中创建了一个对象,因此s1中的引用是堆不是常量池 并且String s2="Goodmorning";在常量池中创建一个String对象“Goodmorning”,其引用存储在s2中。

因此,if(s1==s2)条件为假。

但是jdk7会发生什么?

答案 4 :(得分:6)

第一种情况:

在剪切的第一个代码中,您实际上在字符串池中添加了三个字符串。 1. s1 =“好”
2. s1 =“Goodmorning”(连接后) 3. s2 =“Goodmorining”

在执行if(s1 == s2)时,对象相同但引用不同,因此它是错误的。

第二种情况:

在这种情况下,您正在使用s1.intern(),这意味着如果池已经包含一个等于此String对象的字符串(由equals(Object)方法确定),则返回池中的字符串。否则,将此String对象添加到池中,并返回对此String对象的引用。

  1. s1 =“好”
  2. s1 =“Goodmorning”(连接后)
  3. 对于String s2 =“Goodmorning”,新的String不会添加到池中,您将获得s2的现有String。因此,如果(s1 == s2)返回true。

答案 5 :(得分:5)

您需要使用s1.equals(s2)。将==String个对象一起使用会比较对象引用本身。

编辑:当我运行你的第二个代码片段时,我没有打印出“两者都相等”。

Edit2:说明使用'=='时会比较引用。

答案 6 :(得分:4)

主要有4种比较字符串的方法:

  1. “== operator”:它只是比较字符串对象的引用变量。所以它可能会给你带来意想不到的结果,这取决于你如何创建字符串,即使用String类的构造函数,或者只是使用双引号,因为两者都以不同方式获取内存(分别在堆和池中)。
  2. “equals(Object)method”:这是对象类的方法,由字符串类过载。它比较整个字符串和IS CASE SENSITIVE。
  3. “equalsIgnoreCase(String)method”:这是字符串类的方法,比较整个字符串和IS NOT CASE SENSITIVE。
  4. “compare(String)method”:逐个字符地比较两个字符串,如果返回值为0,则返回它们的差值,这意味着字符串相等。

答案 7 :(得分:3)

每当你在两个String之间进行比较时,不要使用==并使用eqauls(),因为你要比较的对象不是引用:

string1.equals(string2);

答案 8 :(得分:2)

结果代码依赖于运行时:

class Test {
     public static void main(String... args) {
        String s1 = "Good";
        s1 = s1 + "morning";
        System.out.println(s1 == s1.intern()); // Prints true for jdk7, false - for jdk6.
    }
}

如果你这样写:

class Test {
     public static void main(String... args) {
        String s = "GoodMorning";
        String s1 = "Good";
        s1 = s1 + "morning";
        System.out.println(s1 == s1.intern()); // Prints false for both jdk7 and jdk6.
    }
}

原因是'ldc #N'(来自常量池的加载字符串)和String.intern()都将在热点JVM中使用StringTable。有关详细信息,我写了一篇英文池文章:http://aprilsoft.cn/blog/post/307.html