“文本”和新字符串(“文本”)之间有什么区别?


195

以下两个语句之间有什么区别?

String s = "text";

String s = new String("text");


任何人都请回复。字符串a =“ Java”; 字符串b =“ Java”; System.out.println(a == b); true //但System.out.println(“ a == b?” + a == b); // false ...
能源

我不知道何时添加评论(“ a == b?)=>我的结果变为FALSE。为什么?
能源

@Energy结果是false因为操作顺序指示+运算符排在最前面,并串联“ a == b?”。用a创建一个字符串“ a == b?Java”。然后,表达式的"a==b?Java" == b计算结果为false。
艾莉森B

@AllisonB知道了,非常感谢!
能源

Answers:


187

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需要使用它来代替。

但是,如果出于任何原因需要创建两个equals而不是==字符串,则可以使用new String(anotherString)构造函数。但是,需要再次说,这是非常特殊的,并且很少是故意的。

参考资料

相关问题


3
如果我写:String s = new String(“ abc”); 现在我写:String s =“ abc”; Will String s =“ abc”; 在字符串池中创建新的字符串文字?
Kaveesh Kanwal

为什么没人回答前面的问题?
zeds 2015年

2
@KaveeshKanwal不,文字不会重复。如您所见,有2 "abc"s。其中只有一个将进入字符串池,而另一个将引用它。然后s是合适的新对象。
加耶曼

1
@Kaveesh Kanwal-String s = new String(“ abc”)将仅创建一个值为“ abc”的新String对象。第二条语句将检查字符串池中是否已经存在“ abc”字符串文字。如果已经存在,则返回对现有引用的引用,如果不存在,则在字符串池中创建新的文字(“ abc”)。希望它能解决您的查询!
user968813'5

没有“可能”。编译器必须合并字符串文字。JLS 3.10.5
罗恩侯爵

119

字符串文字将进入字符串常量池

下面的快照可以帮助您直观地理解它,以便长时间记住它。

在此处输入图片说明


逐行创建对象:

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 2014年

2
是的,我检查过它不会更改str4的值
CoronaPintu 2014年

@Braj可以提供您的Answer断言的文档吗?
罗勒·布尔克

@Braj:表中“堆”和“池”的标题应该颠倒吗?
拉胡尔·库鲁普

不正确。常量池是在编译时而不是在执行时创建的。不要对未加引号的文本使用引号格式。
罗恩侯爵,

15

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

String s = "text";

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

String s = new String("text");

s 如果以后不使用,则会丢失(有资格使用GC)。

另一方面,字符串文字被重用。如果"text"在类的多个位置使用它,则实际上将是一个且只有一个String(即,对池中同一字符串的多个引用)。


常量池中的字符串永远不会丢失。您是说如果以后不使用会丢失“ s”?
罗恩侯爵

@EJP:是的,我的意思是“ s”。感谢您的关注。我会纠正这个问题。

9

JLS

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

来自JLS 7 3.10.5的相关段落:

而且,字符串文字总是引用类String的相同实例。这是因为使用方法String.intern,对“字符串文字”(或更一般地说,是作为常量表达式的值的字符串(第15.28节))进行了“插入”,以便共享唯一的实例。

示例3.10.5-1 字符串文字

该程序由编译单元(第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"; }

和编译单元:

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

产生输出:

true true true true false true

虚拟机

JVMS 7 5.1说

字符串文字是对String类实例的引用,它是从CONSTANT_String_info结构(第4.4.3节)以类或接口的二进制表示形式派生的。CONSTANT_String_info结构给出了构成字符串文字的Unicode代码点的序列。

Java编程语言要求相同的字符串文字(即,包含相同代码点序列的文字)必须引用String类的相同实例(JLS§3.10.5)。另外,如果在任何字符串上调用String.intern方法,则结果是对该类实例的引用,如果该字符串以文字形式出现,则将返回该实例。因此,以下表达式必须具有值true:

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

为了派生字符串文字,Java虚拟机检查由CONSTANT_String_info结构给出的代码点序列。

  • 如果先前在类String的实例上调用了String.intern方法,该实例包含与CONSTANT_String_info结构给出的序列相同的Unicode代码点序列,则字符串文字派生的结果是对该类String的该实例的引用。

  • 否则,将创建一个新的String类实例,其中包含由CONSTANT_String_info结构给出的Unicode代码点序列;对该类实例的引用是字符串文字派生的结果。最后,调用新的String实例的intern方法。

字节码

查看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

注意如何:

  • 03ldc #2加载相同的常量(文字)
  • 12:创建了一个新的字符串实例(#2作为参数)
  • 35ac作为常规对象进行比较if_acmpne

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

并且上面的JVMS引用似乎表明,只要Utf8指向相同,就会通过加载相同的实例ldc

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

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

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

奖励:将其与没有直接字节码支持(即无模拟)的Integer池进行比较CONSTANT_String_info


2

@Braj:我想你提到了另一种方式。如果我错了请指正我

逐行创建对象:

字符串str1 =新的String(“ java5”)

   Pool- "java5" (1 Object)

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

字符串str2 =“ java5”

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

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

字符串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不涉及str2str3或的价值str4
罗恩侯爵,

1

想像"bla"是像Strings.createString("bla")(pseudo)这样的魔术工厂。工厂保存了所有以这种方式创建的字符串的池。

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

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

new String("")通过绕过字符串文字池,手动创建会覆盖此行为。因此,应始终检查是否使用相等来equals()比较字符序列,而不是对象引用相等。


您所指的“魔术工厂”只不过是Java编译器而已。将此过程编写为好像在运行时发生是错误的。
罗恩侯爵,

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

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


1

任何字符串文字都在字符串文字池中创建,并且该池不允许重复。因此,如果使用相同的文字值初始化两个或多个字符串对象,则所有对象都将指向相同的文字。

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”之后的文字检查在字符串池中是否存在,并且由于在字符串池中不存在而将被创建。以上情况。


0

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


0
String str = new String("hello")

它将检查String常量池是否已经包含String“ hello”?如果存在,则不会在字符串常量池中添加条目。如果不存在,它将在字符串常量池中添加一个条目。

将在堆内存区域中创建一个对象,并且str引用指向在堆内存位置中创建的对象。

如果要str引用包含在字符串常量池中的点对象,则必须显式调用str.intern();

String str = "world";

它将检查String常量池是否已经包含String“ hello”?如果存在,则不会在字符串常量池中添加条目。如果不存在,它将在字符串常量池中添加一个条目。

在以上两种情况下,str引用都指向"world"常量池中存在的字符串。


'它是Java编译器。字符串文字在编译时会在常量池中创建一个唯一条目。这是一个错误的deacribe这个过程就好像它发生在运行时..
马奎斯-洛恩的

您能否清楚地说明此帖子有什么问题?
Jayesh

正如我已经说过的那样,这篇文章中的问题是字符串文字在complle时被合并。不是在执行代码时(如您的回答)。
罗恩侯爵,

@EJP感谢您的回复。您能指出答案错误的确切行吗?我看到以上所有答案都与我写的相同。请帮助,我想纠正我的理解。谢谢。
Jayesh

您已经写了整个过程,好像所有过程都在执行代码行时发生一样,正如我反复告诉您的,情况并非如此。您不能将所有这些都简化为答案中唯一的“精确线”。
罗恩侯爵

0

当您将字符串存储为

String string1 = "Hello";

直接,然后JVM在称为String常量池的单独内存块中以给定的价格创建String对象。

并且每当我们倾向于尝试产生另一个String作为

String string2 = "Hello";

JVM会验证String常量池中是否存在任何具有常量价格的String对象,如果不是这样,则JVM不会创建全新的对象,而是将现有对象的引用分配给新变量。

当我们将String存储为

String string = new String("Hello");

使用new关键字,无论String常量池的内容如何,​​都将创建具有给定价格的全新对象。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.