Java:字面值字符串

6
class A { 

String s4 = "abc";

 static public void main(String[]args ) {

        String s1 = "abc";
        String s2 = "abc";
        String s3 = new String("abc");
        A o = new A();
        String s5 = new String("def");

        System.out.println("s1==s2 : " + (s1==s2));
        System.out.println("s1==s1.intern : " + (s1==s1.intern()));
        System.out.println("s1==s3 : " + (s1==s3));
        System.out.println("s1.intern==s3.intern : " + (s1.intern()==s3.intern()));
        System.out.println("s1==s4 : " + (s1==o.s4));
 }
} 

输出:

s1==s2 : true
s1==s1.intern : true
s1==s3 : false
s1.intern==s3.intern : true
s1==s4 : true

我的问题:

1.对于"String s1 = "abc"会发生什么?我猜想String对象将作为一个interned字符串添加到String类的池中。它被放在哪里?是"permanent generation"还是只是堆(作为String类实例的数据成员)?

2.对于"String s2 = "abc"会发生什么?我猜想不会创建任何对象。但这是否意味着Java解释器需要搜索所有interned字符串?这会导致性能问题吗?

3.似乎String s3 = new String("abc")不使用interned字符串。为什么?

4.String s5 = new String("def")会创建任何新的interned字符串吗?

4个回答

5
  1. 编译器在常量池中为"abc"创建一个字符串对象,并在赋值语句的字节码中生成对它的引用。

  2. 参见(1)。没有搜索,也没有性能问题。

  3. 这会在运行时创建一个新的字符串对象,因为这就是'new'运算符的作用:创建新对象。

  4. 是的,对于"def",但由于(3)的原因,在运行时也会创建一个新的字符串。

第3-4个字符串对象没有被合并。


1
编译器在常量池中创建一个字符串字面量。但是当类被加载时,它会被解析为一个引用。 - Jivings
@Jivings 您的意见是什么?这样的评论对任何人都没有用处。 - user207421
在之前的评论中,我已经说过原因了。编译器不会创建字符串对象,它创建的是UTF-8字面量,在类加载期间解析为指向intern池中对象的引用。 - Jivings

3

1. "String s1 = "abc"会发生什么?

在编译时,该文字的表示形式被写入到包含此代码的类的“常量池”部分的classfile中。

当加载类时,将读取类文件中常量池中字符串文字的表示形式,并从中创建一个新的String对象。然后对此字符串进行内部化,然后将对内部化字符串的引用“嵌入”到代码中。

在运行时,先前创建/内部化的String的引用被分配给s1。(执行此语句时不会创建或内部化任何字符串。)

我猜想String对象作为内部化字符串添加到String类中了?

是的。但是并非在执行代码时添加。

它放在哪里?“永久代”还是堆(作为String Class实例的数据成员)?

它存储在堆的permgen区域中。 (String类没有静态字段。JVM的字符串池是使用本地代码实现的。)

2. "String s2 = "abc"会发生什么?

在加载时什么也不会发生。编译器创建类文件时,重用了与第一次使用文字时使用的相同常量池条目。因此,此语句使用的String引用与先前语句使用的引用相同。

我猜没有创建任何对象。

正确。

但这是否意味着Java解释器需要搜索所有内部化的字符串?这会导致任何性能问题吗?

不会,也不会。Java解释器(或JIT编译的代码)使用与为先前语句创建/嵌入的引用相同的引用。

3.似乎String s3 = new String("abc")不使用内部化字符串。为什么?

情况比较复杂。构造函数调用使用内部化字符串,然后创建一个新的String,并将内部化字符串的字符复制到新String的表示形式中。然后将新创建的字符串分配给s3

为什么?因为JLS将new指定为始终创建一个新对象,并且String构造函数被指定为复制字符。

4.String s5 = new String("def")会创建任何新的内部化字符串吗?

在加载时将创建一个新的内部化字符串(用于“def”),然后在运行时创建一个新的String对象,该对象是内部化字符串的副本。(有关详细信息,请参见上文。)


谢谢,伙计。你的回答看起来很棒。还有一个问题:如果字面量被写入类文件的“常量池”部分,为什么两个类中的2个字面量(相同内容)使用相同的字符串? - Don Li
@DonLi - 常量池是类文件的一个部分。类文件规范不允许一个类文件引用另一个类文件的常量池。 - Stephen C

2

请查看这个关于SO的答案。还可以参考维基百科关于字符串驻留(String Interning)的这篇文章。


-1

String s1 = "abc"; 创建一个新的字符串并将其存储在常量池中。

String s2 = "abc"; 将从常量池中获取与 s1 相同的对象。JVM 这样做是为了提高性能,比创建一个新的字符串更快。

调用 new String() 是多余的,因为它会返回一个新的隐式字符串对象,而不是从常量池中检索它。

正如 Keyser 所说,== 比较字符串的对象相等性,如果它们是相同的对象,则返回 true。当比较字符串内容时,应使用 .equals()


在你编辑(并关闭)回答之前,我已经给你的回答点了踩,因为你说“abc”和new String(“abc”)是同一件事。 - JB Nizet
@EJP 不对,编译器将字符串放置在常量池中作为文字。JVM决定如何在ClassLoading的解析阶段解决这些常量。如果该字符串存在于intern池中,则该文字将被解析为该现有对象。 - Jivings
JVM规范#5.4指出,类型为ONNSTANT_String的常量池条目是java.lang.String的实例。我同意规范对此并不清楚,因为它们还说编译器使用String.intern(),这显然是不正确的。 - user207421
@EJP 这个说法有点含糊。我可以向您保证,JVM在解析期间会检查intern池,并将CONSTANT_String字面量替换为String对象引用。编译器显然不能创建Java对象。 - Jivings
@Jivings 你只是猜测关于踩的事情。这里唯一的证据来自其他人。 - user207421
显示剩余2条评论

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