字符串对象和字符串字面值的区别

259

什么是两者之间的区别?

String str = new String("abc");
并且
String str = "abc";

3
简洁回答:字符串对象是变量;字符串字面值是常量(在引号之间的固定字符序列)。[更多细节] (http://mindbugzz.blogspot.co.uk/2012/05/what-is-difference-between-strings-and.html) - Assad Ebrahim
一个字符串字面值是一个字符串对象,但一个字符串对象不一定是一个字符串字面值。一旦分配给一个引用变量,几乎不可能确定一个给定的字符串对象是否是一个字面值 - Hot Licks
这被标记为一个并不重复的东西的副本。虽然这并不是一个很好的问题,但其他人已经正确地将它列为副本,而副本列表应该以真正重复的东西结尾。 - Don Roby
我在 Sybex 考试中因为回答错误这个问题而失分:所有字符串字面量都会自动实例化为一个字符串对象。。考试似乎认为这总是正确的,即使字面量被内部化到已经存在的对象中? - djangofan
13个回答

224
当您使用字符串字面值时,字符串可以interned,但是当您使用new String("...")时,将会创建一个新的字符串对象。
在这个例子中,两个字符串字面值都指向同一个对象:
String a = "abc"; 
String b = "abc";
System.out.println(a == b);  // true

在这里,创建了两个不同的对象,它们具有不同的引用:

String c = new String("abc");
String d = new String("abc");
System.out.println(c == d);  // false

一般情况下,应尽可能使用字符串字面值表示法。这样更易于阅读,并且可以给编译器优化您的代码的机会。

16
实际上,你通常会看到使用new String(...),并不是因为有人想要这里描述的行为,而是因为他们不知道字符串是不可变的。 因此,你会看到像 b = new String(a); b = b.substring(2); 而不是仅仅 b = a.substring(2)。可能是因为作者认为substring方法会修改被调用的实例。 另外,虽然 "abc" == "abc" 是正确的,但我会说,以这种方式依赖它而不使用equals(...)的代码是聪明和容易混淆的(静态final“常量”除外)。 - George Hawkins
5
这在 JavaScript 中有什么用途? - Randomblue
这个概念的重要性是什么?节省内存吗? - MasterJoe
你也可以使用 new String("...").intern()... - Yousha Aleayoub
当您使用字符串字面量时,根据JLS的规定,该字符串将被内部化。这是毫无疑问的。编译器有义务对这些字面量进行池化。 - user207421

90

字符串字面值 是Java语言的一个概念。以下是一个字符串字面值:

"a String literal"

一个 字符串对象java.lang.String 类的一个实例。

String s1 = "abcde";
String s2 = new String("abcde");
String s3 = "abcde";

这些都是有效的,但有一点区别。 s1将引用一个interned(内部化)的字符串对象。这意味着字符序列"abcde"将存储在中央位置,并且每当相同的文字"abcde"再次出现时,JVM将不会创建新的字符串对象,而是使用缓存的字符串的引用。

s2保证是新的字符串对象,所以在这种情况下我们有:

s1 == s2 // is false
s1 == s3 // is true
s1.equals(s2) // is true

18
很多语言都有字符串字面量的概念 :) - mrk
4
这是否意味着字面上的字符串"abc"仍然像new String("abc")一样是一个对象,唯一的区别是它存储在常量池中而不是堆中? - yifei
是的,@yifei,就是这个意思。 - atamanroman
1
所以这意味着“字符串字面量更好”,因为它再次使用相同的对象,而不是新的空间? - Asif Mushtaq
1
那么在你的情况下,被 s2 引用的字符数组 'abcde' 存储在堆中而不是字符串池中?所以如果你创建 100 个 String 对象,比如 new String("abc"),它们会在堆中有 100 份拷贝? - liam xu
显示剩余2条评论

42

长篇回答可以在这里找到,所以我会给你一个简短的回答。

当你这样做:

String str = "abc";
您正在调用 String 上的 intern() 方法。此方法引用了一个内部的字符串对象池。如果您在调用 intern() 方法时的字符串已经存在于池中,则将该字符串的引用分配给 str。否则,新的字符串将被放入池中,并将其引用分配给 str
给出以下代码:
String str = "abc";
String str2 = "abc";
boolean identity = str == str2;
当你使用 == 来检查对象标识时(实际上是在问:这两个引用是否指向同一个对象?),你会得到 true
但是,你不需要强制使用 intern() 来使 Strings 变为唯一的。你可以通过以下方式强制在堆上创建一个新的 Object
String str = new String("abc");
String str2 = new String("abc");
boolean identity = str == str2;
在这个例子中,strstr2是对不同的Objects的引用,它们都没有被interned,因此当您使用==测试Object的身份时,您将得到false
在良好的编码实践方面:不要使用==检查字符串相等性,而是应该使用.equals()

1
你并没有通过引用字面量来调用intern()。你要依赖编译器已经将它们汇集并创建了常量区中的String对象。 - user207421
2
EJB,编译器从何时开始创建对象?编译后的字节码可能会在10年后在不同的机器上运行。JVM的工作是创建String对象。根据机器语言规范(3.10.5),“字符串文字是对String类实例的引用”。规范甚至承诺它将是跨不同类和包相同的实例。您可能正在考虑“常量表达式”。代码“Hello”+“ World”将被编译器重写为“Hello World”。 - Martin Andersson
@MartinAnderson自1995年或之前JLS要求以来,遇到字符串字面量时的行为是(1)编译器在类中分组池化,(2)类装载器在JVM中分组池化。所有这些操作都发生在引用它的代码行执行之前很长一段时间内,字符串字面量才会被“遇到”。 - user207421
我只是因为解释而赞同了这个答案。感谢您让这个概念更加清晰。 - Nick Rolando

40

由于字符串是不可变的,所以当你执行以下操作时:

String a = "xyz"

在创建字符串时,JVM会在字符串池中搜索是否已经存在一个字符串值"xyz"。如果是这样,那么'a'将只是该字符串的引用,并且不会创建新的String对象。

但如果你使用:

String a = new String("xyz")

即使 "xyz" 已经存在于字符串常量池中,你也可以通过这种方式强制 JVM 创建一个新的 String 引用。

如需更多信息,请参阅此处


2
链接现在已经失效了,我猜这篇文章应该是:http://javatechniques.com/blog/string-equality-and-interning/。 - Graham Griffiths

17

"abc" 是一个字符串字面量。

在Java中,这些字面量字符串会被内部池化,并且相同的String实例会在你代码中声明相同的字符串字面量时被重用。因此,"abc" == "abc"将始终为true,因为它们都是相同的String实例。

使用String.intern()方法,你可以将任何你喜欢的字符串添加到内部池化的字符串中,这些字符串将一直保留在内存中,直到Java关闭。

另一方面,使用new String("abc")将在内存中创建一个新的字符串对象,逻辑上与"abc"字面量相同。"abc" == new String("abc")将始终为false,因为虽然它们逻辑上相等,但它们指向不同的实例。

将字符串字面量放入String构造器中没有意义,只会不必要地占用更多的内存。


1
“内部池化的字符串将一直保存在内存中,直到Java退出。”我认为至少在现代JVM中,这种情况已经不再适用了,因为垃圾回收器也会清理未使用的对象。您能确认一下吗? - Guido

7

String是Java中的一个类,与其他编程语言不同。因此,对于每个类,对象的声明和初始化都是必要的。

String st1 = new String();

或者

String st2 = new String("Hello"); 
String st3 = new String("Hello");

这里,st1st2st3是不同的对象。

也就是说:

st1 == st2 // false
st1 == st3 // false
st2 == st3 // false

因为st1st2st3引用了3个不同的对象,而==检查的是内存位置的相等性,因此结果是如此。但是:
st1.equals(st2) // false
st2.equals(st3) // true

在这里,.equals()方法检查内容,st1 = ""st2 = "hello"st3 = "hello"的内容。因此得出结果。

在字符串声明的情况下

String st = "hello";

在这里,调用了String类的intern()方法,它会检查"hello"是否在内部池中,如果没有,则会将其添加到内部池中;如果"hello"已经存在于内部池中,则st将指向现有"hello"的内存。因此,在以下情况下:
String st3 = "hello";
String st4 = "hello"; 

在这里:

st3 == st4 // true

因为st3st4指向相同的内存地址。

另外:

st3.equals(st4);  // true as usual

@CaZbaN1 为解释 intern() 方法在确定值是否存在于堆上而做出的贡献,点赞加一。 - ha9u63a7

6
在第一种情况下,创建了两个对象。
在第二种情况下,只创建了一个对象。
尽管两种方式都是引用到字符串 "abc"。

你能解释一下第一种情况下如何创建obj对象吗? - Gautam Savaliya
在第一种情况下,将创建一个对象来缓存字符串以便在代码中进一步重用,并且将在堆内存中创建另一个对象。您可以参考此问题https://dev59.com/ZXE95IYBdhLWcg3wCJbk了解有关字符串池的更多详细信息。 - Sai Upadhyayula

5
一些拆卸总是很有趣...
$ cat Test.java 
public class Test {
    public static void main(String... args) {
        String abc = "abc";
        String def = new String("def");
    }
}

$ javap -c -v Test
Compiled from "Test.java"
public class Test extends java.lang.Object
  SourceFile: "Test.java"
  minor version: 0
  major version: 50
  Constant pool:
const #1 = Method  #7.#16;  //  java/lang/Object."<init>":()V
const #2 = String  #17;     //  abc
const #3 = class   #18;     //  java/lang/String
const #4 = String  #19;     //  def
const #5 = Method  #3.#20;  //  java/lang/String."<init>":(Ljava/lang/String;)V
const #6 = class   #21;     //  Test
const #7 = class   #22;     //  java/lang/Object
const #8 = Asciz   <init>;
...

{
public Test(); ...    

public static void main(java.lang.String[]);
  Code:
   Stack=3, Locals=3, Args_size=1
    0:    ldc #2;           // Load string constant "abc"
    2:    astore_1          // Store top of stack onto local variable 1
    3:    new #3;           // class java/lang/String
    6:    dup               // duplicate top of stack
    7:    ldc #4;           // Load string constant "def"
    9:    invokespecial #5; // Invoke constructor
   12:    astore_2          // Store top of stack onto local variable 2
   13:    return
}

5

除了已经发布的答案,还可以查看这篇有关javaranch的优秀文章。


3
根据String类文档,它们是等效的。 String(String original)的文档还说:除非需要原始字符串的显式副本,否则使用此构造函数是不必要的,因为字符串是不可变的。
寻找其他响应,因为似乎Java文档引导人入了歧途 :(

2
它们绝对不等价。一个每次执行时都会构建一个新的字符串,而另一个则不会。涉及的字符串将是相等的,但这并不意味着这两个表达式的行为完全相同。 - Jon Skeet
2
@Michal:是的,文档有点误导。在某些情况下,那个构造函数确实很有用,因为它可以有效地将新字符串“修剪”到指定大小。 - Jon Skeet
@Michał Niklas:对于混乱的Javadoc(这在Java中非常普遍),我给予+1。因为它提醒我们要对Javadocs中写的一切持怀疑态度 :) - SyntaxT3rr0r
我对上面的一些评论有些不赞同。@Jon,@Michal:文档没有说字符串相等,只是new()一个字符串是不必要的。@Jon:鉴于你的声誉,我不敢质疑你的答案,但我没有看到克隆的字符串字面量在任何方面与原始字符串“修剪”有任何联系。我想使用new("literal")的唯一理由是获得一个保证不会与否则相同(相等)的原始字符串==的字符串。 - Carl Smotricz
2
@Carl,@Michael:更详细地解释Jon Skeet的评论:使用str.substring()返回一个新字符串,引用str相同的字符数组。如果您不再需要str,则使用new String(str.substring(..))。否则,您可能会使用比必要更多的内存。 - Eyal Schneider
显示剩余6条评论

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