55
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 中产生不同的输出?

4

9 回答 9

27

似乎 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.
   }
}

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

在这里找到:JDK 7 中解决的重要 RFE

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

不确定这是否是一个错误以及来自哪个版本...... JLS 3.10.5 状态

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

所以问题是如何解释预先存在的,编译时或执行时:“早安”是否预先存在?
我更喜欢它在 7 之前实施的方式......

于 2011-08-17T09:46:36.277 回答
25

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

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,则返回对该对象的引用,否则创建新字符串(等于this),放入池中,并返回对该创建的实例的引用。

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

Java 6 和 Java 7 都没有违反方法的约定

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

于 2011-08-28T23:59:56.190 回答
9

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

String.intern 方法的 javadoc解释:

公共字符串实习生()

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

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

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

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

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

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

因此,编译器无需实习即可查看 Java 代码中的常量并从中构建其常量池。String 类维护了一个不同的池,实习检查通过池传入的字符串并确保引用是唯一的(这样 == 才会起作用)。

于 2011-08-15T13:16:57.773 回答
7

In jdk6: String s1="Good"; creates a String object "Good" in constant pool.

s1=s1+"morning"; creates another String object "morning" in constant pool but this time actually JVM do: s1=new StringBuffer().append(s1).append("morning").toString();.

Now as the new operator creates an object in heap therefore the reference in s1 is of heap not constant pool and the String s2="Goodmorning"; creates a String object "Goodmorning" in constant pool whose reference is stored in s2.

Therefore, if(s1==s2) condition is false.

But what happens in jdk7?

于 2011-08-17T03:35:48.270 回答
6

第一种情况:

在第一个代码中,您实际上是在字符串池中添加三个字符串。1. s1 = "Good"
2. s1 = "Goodmorning"(连接后) 3. s2 = "Goodmorining"

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

第二种情况:

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

  1. s1 =“好”
  2. s1 =“早安”(连接后)
  3. 对于 String s2="Goodmorning",不会将新字符串添加到池中,并且您会获得 s2 现有字符串的引用。因此 if(s1==s2) 返回真。
于 2011-08-18T08:21:15.183 回答
5

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

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

Edit2:澄清了在使用“==”时比较引用。

于 2011-08-15T13:15:04.227 回答
4

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

  1. “== 运算符”:它只是比较字符串对象的引用变量。因此,它可能会给您带来意想不到的结果,具体取决于您创建字符串的方式,即使用 String 类的构造函数或简单地使用双引号,因为两者获取内存的方式不同(分别在堆和池中)。
  2. “equals(Object) 方法”:这是对象类的方法,被字符串类重载。它比较整个字符串并且区分大小写。
  3. “equalsIgnoreCase(String) 方法”:这是字符串类的方法,比较整个字符串并且不区分大小写。
  4. “compares(String) 方法”:逐个字符比较两个字符串,如果返回值为 0,则返回它们的差异,这意味着字符串相等。
于 2011-08-15T15:47:55.020 回答
3

每当您在两个字符串之间进行比较时,请不要使用==和使用,eqauls()因为您正在比较对象而不是引用:

string1.equals(string2);
于 2011-08-15T13:17:43.180 回答
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

于 2012-05-26T02:11:56.267 回答