下面这两种说法有什么不同?

String s = "text";

String s = new String("text");

当前回答

一个是在字符串常量池中创建一个字符串

String s = "text";

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

String s = new String("text");

如果以后未使用,则会丢失(符合GC的条件)。

另一方面,字符串字面值被重用。如果你在你的类的多个地方使用“text”,它实际上将是一个且只有一个String(即在池中多次引用同一个字符串)。

其他回答

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

把“bla”想象成一个魔法工厂,就像Strings.createString(“bla”)(pseudo)。工厂保存了所有以这种方式创建的字符串池。

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

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

手动创建新的String("")通过绕过字符串字面量池来覆盖这种行为。因此,应该始终使用equals()检查相等性,它比较字符序列,而不是对象引用相等性。

JLS

这个概念被JLS称为“实习”。

来自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说:

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

和主要:

 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

注意:

0和3:加载相同的LDC #2常量(字面量) 12:创建一个新的字符串实例(使用#2作为参数) 35: a和c作为常规对象与if_acmpne进行比较

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

它有一个专用的CONSTANT_String_info结构,不像常规对象(例如new String) 该结构指向包含数据的CONSTANT_Utf8_info结构体。这是表示字符串的唯一必要数据。

上面的jvm引用似乎是说,只要Utf8指向的是相同的,那么ldc就加载相同的实例。

我已经对字段做了类似的测试,并且:

String s = "abc"通过ConstantValue Attribute指向常量表 非final字段没有这个属性,但仍然可以用LDC初始化

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

额外的好处:将其与Integer池进行比较,后者没有直接的字节码支持(即没有CONSTANT_String_info模拟)。

字符串字面值将进入字符串常量池。

下面的快照可能会帮助你从视觉上理解它,从而更长时间地记住它。


逐行创建对象:

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社区的进一步阅读

当你使用一些东西,如new String(“Hello World”),SpotBugs代码分析工具,抱怨一个性能问题。

这个问题的描述显示了新字符串和常量字符串的区别

使用java.lang.String(String)构造函数会浪费内存,因为 这样构造的对象将在功能上难以区分 作为参数传递的字符串。只需使用参数String 直接。 错误类型和模式:Dm - DM_STRING_CTOR