String s = "text";
String s = new String("text");
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)
构造函数。需要再次强调的是,这是非常奇怪的,很少有意义。
"abc"
。只有其中一个将进入字符串池,另一个将引用它。然后是 s
,它将成为一个全新的对象。 - Kayaman字符串字面量将被放入字符串常量池。
下面的快照可能会帮助您通过视觉方式更好地理解并更长时间地记住它。
逐行创建对象:
String str1 = new String("java5");
在构造函数中使用字符串字面量"java5",将会在字符串常量池中存储一个新的字符串值。 通过使用new操作符,在堆上创建一个以"java5"为值的新字符串对象。
String str2 = "java5"
“str2”引用已指向字符串常量池中已存储的值。
String str3 = new String(str2);
在堆中创建一个新的字符串对象,该对象的值与“str2”引用的值相同。
String str4 = "java5";
引用“str4”指向已经存储在字符串常量池中的值
总对象数:堆 - 2,池 - 1
在字符串常量池中创建一个字符串。
String s = "text";
另一个在常量池中创建字符串("text"
),另一个在普通堆空间中创建字符串(s
)。这两个字符串将具有相同的值,即“text”。
String s = new String("text");
s
如果后面没有被使用,它将会被垃圾回收。
另一方面,字符串常量是被重用的。如果你在类的多处使用 "text"
,实际上只有一个 String(即对字符串池中同一个字符串的多个引用)。
这个概念在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
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
请注意:
0
和 3
: 加载了相同的常量 ldc #2
(字面值)12
: 创建了一个新的字符串实例(带有参数 #2
)35
: 使用 if_acmpne
对 a
和 c
进行常规对象比较常量字符串的表示在字节码上非常神奇:
new String
)以上JVMS的引用似乎表明,每当指向的Utf8相同时,ldc
将加载相同的实例。
我已经对字段进行了类似的测试:
static final String s = "abc"
通过ConstantValue属性指向常量表ldc
初始化结论:字符串池有直接的字节码支持,并且内存表示高效。
额外奖励:与没有直接字节码支持(即无CONSTANT_String_info
类似物)的整数池进行比较。
String obj1 = "abc";
String obj2 = "abc";
“obj1”和“obj2”将指向同一个字符串字面量,字符串字面量池中只有一个“abc”字面量。
当我们使用new关键字创建String类对象时,所创建的字符串存储在堆内存中。然而,任何传递给String类构造函数的字符串字面量都存储在字符串池中。如果我们使用相同的值通过new操作符创建多个对象,则每次都会在堆中创建一个新对象,因此应避免使用new操作符。
String obj1 = new String("abc");
String obj2 = new String("abc");
String str1 = "abc";
String str2 = "abc" + "def";
str1 = "xyz";
str2 = str1 + "ghi";
@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)
理解它们之间的区别的一种简单方法如下:
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() 将始终创建一个新实例。
想象 "bla"
就像一个魔法工厂,例如 Strings.createString("bla")
(伪代码)。该工厂维护了一个这种方式创建的字符串池。
如果被调用,它会检查池中是否已经有此值的字符串。如果是,则返回此字符串对象,因此通过这种方式获得的字符串确实是相同的对象。
如果没有找到匹配的字符串,它将在内部创建一个新的字符串对象,将其保存在池中,然后返回它。因此,当查询相同的字符串值时,它将返回相同的实例。
手动创建 new String("")
将覆盖此行为,因为它绕过了字符串字面量池。所以应该始终使用 equals()
方法来比较字符序列而不是对象引用相等性来进行相等性检查。
从程序员的角度来看,尽管它看起来相同,但性能影响很大。你几乎总是想使用第一种形式。
当您将字符串存储为
String string1 = "Hello";
直接地,JVM 会在一个名为字符串常量池的单独内存块中创建一个具有给定价格的字符串对象。
每当我们试图创建另一个字符串时,
String string2 = "Hello";
String string = new String("Hello");
false
,因为操作顺序规定 + 运算符先执行,将 "a==b ?" 与 a 进行连接,创建一个字符串 "a==b?Java"。然后表达式"a==b?Java" == b
的结果为 false。 - Allison B