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

Moh*_*sal 54 java string string-interning java-6 java-7

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");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

use*_*421 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.
   }
}
Run Code Online (Sandbox Code Playgroud)

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

在此处找到:JDK 7中的重要RFE

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

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

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

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


And*_*rey 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.
    }
}
Run Code Online (Sandbox Code Playgroud)

让我们考虑String#intern一个黑盒子.根据运行的几个测试用例,我得出结论,实现如下:

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

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

Java 6和Java 7都没有破坏方法契约.

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


Nat*_*hes 9

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

String.intern方法的javadoc 解释了:

public String intern()

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

最初为空的字符串池由String类私有维护.

调用实习方法时,如果池已经包含等于此字符串对象的字符串(由equals(Object)方法确定),则返回池中的字符串.否则,将此String对象添加到池中,并返回对此String对象的引用.

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

所有文字字符串和字符串值常量表达式都是实体.字符串文字在Java语言规范的§3.10.5中定义

返回:与此字符串具有相同内容的字符串,但保证来自唯一字符串池.

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

  • @Mohammad:它在编译类时检查常量池中的内容.所以它不能考虑字符串连接等.实习生在运行时重新分配引用. (3认同)

Moh*_*sal 7

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

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

现在,当new操作符在堆中创建一个对象时,引用in s1是堆非常量池,并在常量池中String s2="Goodmorning";创建一个String对象"Goodmorning",其存储引用s2.

因此,if(s1==s2)条件是错误的.

但是在jdk7中会发生什么?


Sac*_*kar 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.

  • 对于JDK7,第3点有效(仅?).使用JDK6时,`s1 == s2`返回**false**,因为`intern()`显然在池中存储了不同的实例/引用(相同的字符). (2认同)

Dat*_*jam 5

你需要使用s1.equals(s2).使用==with String对象比较对象引用本身.

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

Edit2:澄清了使用'=='时引用的比较.