"Text"和new String("text")有什么区别?

241
这两个语句有什么区别?
String s = "text";

String s = new String("text");

相关主题:JEP 192: G1中的字符串去重 - Basil Bourque
请有人回复这个。String a = "Java"; String b = "Java"; System.out.println(a == b); // true,但是System.out.println("a==b?"+a == b); // false... - Energy
我不明白为什么当我添加了一些注释("a==b ?)=> 我的结果变成了FALSE。为什么? - Energy
@Energy 结果为 false,因为操作顺序规定 + 运算符先执行,将 "a==b ?" 与 a 进行连接,创建一个字符串 "a==b?Java"。然后表达式 "a==b?Java" == b 的结果为 false。 - Allison B
@AllisonB 已经明白了,非常感谢! - Energy
13个回答

230

new String("text"); 明确地创建了一个新的、有引用差异的 String 对象实例;String s = "text"; 如果有可用的字符串常量池中的实例,则可以重用该实例。

很少需要使用new String(anotherString)构造函数。从API中可以看到:

String(String original):初始化一个新创建的String对象,使其表示与参数相同的字符序列;换句话说,新创建的字符串是参数字符串的副本。除非需要显式复制原始字符串,否则使用此构造函数是不必要的,因为字符串是不可变的。

相关问题


引用差异的含义

看下面这段代码:

    String s1 = "foobar";
    String s2 = "foobar";

    System.out.println(s1 == s2);      // true

    s2 = new String("foobar");
    System.out.println(s1 == s2);      // false
    System.out.println(s1.equals(s2)); // true

== 用于两个引用类型时,是一个引用身份比较。两个对象如果 equals 是相等的,但不一定是 == 相等的。在引用类型上使用 == 通常是错误的,大多数情况下应该使用 equals

然而,如果由于某种原因需要创建两个相等但不相同的字符串,可以使用 new String(anotherString) 构造函数。需要再次强调的是,这是非常奇怪的,很少有意义。

参考资料

相关问题


3
如果我写:String s = new String("abc"); 现在我写:String s = "abc"; 那么String s = "abc"; 会在字符串池中创建一个新的字符串字面值吗? - Kaveesh Kanwal
为什么没有人回答之前的问题? - zeds
2
@KaveeshKanwal 不会复制字面量。正如您所看到的,有两个 "abc"。只有其中一个将进入字符串池,另一个将引用它。然后是 s,它将成为一个全新的对象。 - Kayaman
1
@Kaveesh Kanwal - String s = new String("abc") 只会创建一个值为 "abc" 的新字符串对象。第二个语句将检查字符串池中是否已经存在任何 "abc" 字符串字面量。如果已经存在,则返回对现有字符串的引用;如果不存在,则在字符串池中创建新的字面量 ("abc")。希望这解决了你的问题!! - user968813
毫无疑问,编译器必须汇集字符串字面量。[JLS 3.10.5] (https://docs.oracle.com/javase/specs/jls/se10/html/jls-3.html#jls-3.10.5)。 - user207421
当您想要获取子字符串但不保留对原始字符串的引用时,new String()非常有用(人们称substring()方法会这样做)。 - Kitten

147

字符串字面量将被放入字符串常量池

下面的快照可能会帮助您通过视觉方式更好地理解并更长时间地记住它。

enter image description here


逐行创建对象:

String str1 = new String("java5");

在构造函数中使用字符串字面量"java5",将会在字符串常量池中存储一个新的字符串值。 通过使用new操作符,在堆上创建一个以"java5"为值的新字符串对象。

String str2 = "java5"

“str2”引用已指向字符串常量池中已存储的值。

String str3 = new String(str2);

在堆中创建一个新的字符串对象,该对象的值与“str2”引用的值相同。

String str4 = "java5";

引用“str4”指向已经存储在字符串常量池中的值

总对象数:堆 - 2,池 - 1

请参阅Oracle社区的更多信息


1
好的回答。但我们想知道,如果我现在要更改str1的值为“java6”,那么它会改变str4的值吗? - CoronaPintu
3
是的,我已经检查过了,它不会改变str4的值。 - CoronaPintu
@Braj,你能提供你回答的断言的文档吗? - Basil Bourque
@Braj:表格中“Heap”和“pool”的标题是否应该反过来? - Rahul Kurup
1
不正确。常量池是在编译时创建的,而不是在执行时创建的。不要为未引用的文本使用引号格式。 - user207421
显示剩余2条评论

16

字符串常量池中创建一个字符串。

String s = "text";

另一个在常量池中创建字符串("text"),另一个在普通堆空间中创建字符串(s)。这两个字符串将具有相同的值,即“text”。

String s = new String("text");

s 如果后面没有被使用,它将会被垃圾回收。

另一方面,字符串常量是被重用的。如果你在类的多处使用 "text",实际上只有一个 String(即对字符串池中同一个字符串的多个引用)。


常量池中的字符串永远不会丢失。您的意思是如果稍后未使用,字符串's'会丢失吗? - user207421
@EJP:是的,我确实是指“s”。谢谢您注意到了。我会更正问题。 - user159088

10

JLS

这个概念在JLS中被称为“interning”。

JLS 7 3.10.5相关段落:

Moreover, a string literal always refers to the same instance of class String. This is because string literals - or, more generally, strings that are the values of constant expressions (§15.28) - are "interned" so as to share unique instances, using the method String.intern.

Example 3.10.5-1. String Literals

The program consisting of the compilation unit (§7.3):

package testPackage;
class Test {
    public static void main(String[] args) {
        String hello = "Hello", lo = "lo";
        System.out.print((hello == "Hello") + " ");
        System.out.print((Other.hello == hello) + " ");
        System.out.print((other.Other.hello == hello) + " ");
        System.out.print((hello == ("Hel"+"lo")) + " ");
        System.out.print((hello == ("Hel"+lo)) + " ");
        System.out.println(hello == ("Hel"+lo).intern());
    }
}
class Other { static String hello = "Hello"; }

and the compilation unit:

package other;
public class Other { public static String hello = "Hello"; }

produces the output:

true true true true false true

JVMS

JVMS 7 5.1 says:

A string literal is a reference to an instance of class String, and is derived from a CONSTANT_String_info structure (§4.4.3) in the binary representation of a class or interface. The CONSTANT_String_info structure gives the sequence of Unicode code points constituting the string literal.

The Java programming language requires that identical string literals (that is, literals that contain the same sequence of code points) must refer to the same instance of class String (JLS §3.10.5). In addition, if the method String.intern is called on any string, the result is a reference to the same class instance that would be returned if that string appeared as a literal. Thus, the following expression must have the value true:

("a" + "b" + "c").intern() == "abc"

To derive a string literal, the Java Virtual Machine examines the sequence of code points given by the CONSTANT_String_info structure.

  • If the method String.intern has previously been called on an instance of class String containing a sequence of Unicode code points identical to that given by the CONSTANT_String_info structure, then the result of string literal derivation is a reference to that same instance of class String.

  • Otherwise, a new instance of class String is created containing the sequence of Unicode code points given by the CONSTANT_String_info structure; a reference to that class instance is the result of string literal derivation. Finally, the intern method of the new String instance is invoked.

字节码

查看OpenJDK 7上的字节码实现也是有益的。

如果我们反编译:

public class StringPool {
    public static void main(String[] args) {
        String a = "abc";
        String b = "abc";
        String c = new String("abc");
        System.out.println(a);
        System.out.println(b);
        System.out.println(a == c);
    }
}

我们在常量池中有:

#2 = String             #32   // abc
[...]
#32 = Utf8               abc

main

 0: ldc           #2          // String abc
 2: astore_1
 3: ldc           #2          // String abc
 5: astore_2
 6: new           #3          // class java/lang/String
 9: dup
10: ldc           #2          // String abc
12: invokespecial #4          // Method java/lang/String."<init>":(Ljava/lang/String;)V
15: astore_3
16: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;
19: aload_1
20: invokevirtual #6          // Method java/io/PrintStream.println:(Ljava/lang/String;)V
23: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;
26: aload_2
27: invokevirtual #6          // Method java/io/PrintStream.println:(Ljava/lang/String;)V
30: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;
33: aload_1
34: aload_3
35: if_acmpne     42
38: iconst_1
39: goto          43
42: iconst_0
43: invokevirtual #7          // Method java/io/PrintStream.println:(Z)V

请注意:

  • 03: 加载了相同的常量 ldc #2 (字面值)
  • 12: 创建了一个新的字符串实例(带有参数 #2
  • 35: 使用 if_acmpneac 进行常规对象比较

常量字符串的表示在字节码上非常神奇:

以上JVMS的引用似乎表明,每当指向的Utf8相同时,ldc将加载相同的实例。

我已经对字段进行了类似的测试:

  • static final String s = "abc" 通过ConstantValue属性指向常量表
  • 非final字段没有该属性,但仍可以使用ldc初始化

结论:字符串池有直接的字节码支持,并且内存表示高效。

额外奖励:与没有直接字节码支持(即无CONSTANT_String_info类似物)的整数池进行比较。


3
任何字符串文字都会在字符串文字池中创建,该池不允许任何重复。因此,如果两个或更多字符串对象使用相同的文字值初始化,则所有对象将指向相同的文字。
String obj1 = "abc";
String obj2 = "abc";

“obj1”和“obj2”将指向同一个字符串字面量,字符串字面量池中只有一个“abc”字面量。

当我们使用new关键字创建String类对象时,所创建的字符串存储在堆内存中。然而,任何传递给String类构造函数的字符串字面量都存储在字符串池中。如果我们使用相同的值通过new操作符创建多个对象,则每次都会在堆中创建一个新对象,因此应避免使用new操作符。

String obj1 = new String("abc");
String obj2 = new String("abc");

"obj1" 和 "obj2" 将指向堆中的两个不同对象,而字符串字面量池中仅有一个 "abc" 字面量。另外需要注意的是,对于字符串的任何新赋值或连接操作都会在内存中创建一个新的对象。
String str1 = "abc";
String str2 = "abc" + "def";
str1 = "xyz";
str2 = str1 + "ghi";

现在看上面的例子:
第1行:"abc"字面量存储在字符串池中。
第2行:"abcdef"字面量被存储在字符串池中。
第3行:一个新的"xyz"字面量被存储在字符串池中,"str1"开始指向这个字面量。
第4行:由于该值是通过附加到另一个变量而生成的,结果存储在堆内存中,被附加的字面量"ghi"将被检查其是否存在于字符串池中,并且将被创建,因为在上述情况下它不存在。

2

@Braj: 我认为你说反了。如果我错了,请纠正我

逐行创建对象:

String str1 = new String("java5")

   Pool- "java5" (1 Object)

   Heap - str1 => "java5" (1 Object)

字符串 str2 = "java5"

  pool- str2 => "java5" (1 Object)

  heap - str1 => "java5" (1 Object)

String str3 = new String(str2)

字符串 str3 = 新的字符串(str2)

  pool- str2 => "java5" (1 Object)

  heap- str1 => "java5", str3 => "java5" (2 Objects)

字符串 str4 = "java5"

  pool - str2 => str4 => "java5" (1 Object)

  heap - str1 => "java5", str3 => "java5" (2 Objects)

"str1" 在任何情况下都不涉及 "str2"、"str3" 或 "str4" 的值。 - user207421

1

理解它们之间的区别的一种简单方法如下:

String s ="abc";
String s1= "abc";
String s2=new String("abc");

        if(s==s1){
            System.out.println("s==s1 is true");
        }else{
            System.out.println("s==s1 is false");
        }
        if(s==s2){
            System.out.println("s==s2 is true");
        }else{
            System.out.println("s==s2 is false");
        }

输出为

s==s1 is true
s==s2 is false

因此,new String() 将始终创建一个新实例。


1

想象 "bla" 就像一个魔法工厂,例如 Strings.createString("bla")(伪代码)。该工厂维护了一个这种方式创建的字符串池。

如果被调用,它会检查池中是否已经有此值的字符串。如果是,则返回此字符串对象,因此通过这种方式获得的字符串确实是相同的对象。

如果没有找到匹配的字符串,它将在内部创建一个新的字符串对象,将其保存在池中,然后返回它。因此,当查询相同的字符串值时,它将返回相同的实例。

手动创建 new String("") 将覆盖此行为,因为它绕过了字符串字面量池。所以应该始终使用 equals() 方法来比较字符序列而不是对象引用相等性来进行相等性检查。


你所提到的“魔法工厂”不过是Java编译器而已。将这个过程描述成运行时发生的事情是错误的。 - user207421

1

从程序员的角度来看,尽管它看起来相同,但性能影响很大。你几乎总是想使用第一种形式。


0

当您将字符串存储为

String string1 = "Hello";

直接地,JVM 会在一个名为字符串常量池的单独内存块中创建一个具有给定价格的字符串对象。

每当我们试图创建另一个字符串时,

String string2 = "Hello";

JVM会验证字符串常量池中是否存在任何具有常量价格的String对象,如果存在,则JVM不会创建新对象,而是将现有对象的引用分配给新变量。
当我们将字符串存储为
String string = new String("Hello");

使用new关键字,无论字符串常量池中的内容如何,都会创建一个具有给定价格的全新对象。

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