Java中的字符串空构造函数

13

我正在研究String API,然后突然发现一个空字符串构造函数,即我们可以使用String s = new String()来构造一个空的字符串对象。

我想知道它是否有任何用途?


2
有趣的问题。String() 的 Javadocs 表示:“请注意,由于字符串是不可变的,因此不必使用此构造函数。” - Steve Kuo
1
我认为如果您使用未初始化的s对象,Java会给出空指针异常。并且这样的s包含空字符串。 - Aliaksei Bulhak
1
仅用于初始化:初始化一个新创建的String对象,使其表示一个空字符序列。 - huseyin tugrul buyukisik
1
这是一个好问题,与主题相关,不应该被删除。那个构造函数的存在确实很奇怪,人们可能会想它是否是库设计者的错误,或者仅为了向后兼容而保留,或者它是否有微妙的用例。 - Raedwald
6个回答

11

当使用 String s = new String(); 时,会在堆上创建一个非文字常量的字符串对象,该对象会被垃圾回收。

而当使用 String s = ""; 时,会创建一个字符串常量,如果通过默认加载器引用到该对象,它将永远不会被垃圾回收。

请参考下面链接中提出的问题。虽然这可能与您的问题没有直接关系,但它肯定会帮助您更好地理解这个概念。

字符串文字池是对字符串对象的引用集合还是对象的集合


一个字符串字面量可以被垃圾回收,尽管一些常见的字符串可能在JVM生命周期中始终被强引用。 - irreputable
我听说从Java 7开始,interned字符串可以被垃圾回收。 - Steve Kuo
2
我真的很难想出一个场景,其中空字符串的实例是否可以被垃圾回收真正重要。 - Buhb
"" 放在字符串常量池中,而 new String() 放在堆上。 - CᴴᴀZ
这是非常不正确的——它永远不会被垃圾回收。 - Eugene

4

创建一个空字符串,似乎有一些有限的用途。

如果您将通过拼接构建一个String,并且不使用例如StringBuilder,则您的代码可以从以下任一开始。

String result = new String();
String result = "";
String result = "first part of string";

// ...
result += "append to the result";

第一种和第二种并不相等,你应该选择使用""来初始化,因为这可以利用字符串常量池

如果可以使用字面量"",就没有必要调用new String()。这两者并不等价,因为构造函数会创建一个新实例,但""字面量使用运行时池中的实例。 - Natix
@Natix编辑了,我的回答确实_暗示_这两者是等价的。 - pb2q

3

一个小例子... 字符串可以被垃圾回收

System.out.println(1 + new String() + 2);

代替
System.out.println(1 + "" + 2);

0
根据文档,这个构造函数创建一个空序列。
public String()

Initializes a newly created String object so that it represents an empty character sequence. Note that use of this constructor is unnecessary since Strings are immutable.

如果你需要一个空序列,那是有意义的。
但通常情况下,在对字符串进行更改之前使用空构造函数是不必要的,因为你并没有改变字符串。实际上,当你使用 += 运算符进行更改时,你正在创建另一个不可变的字符串,而不是改变原来的字符串。
关于这个主题,请查看这个问题:如何工作字符串对象(像不可变对象)?

0

首先一些背景

由于Java中的字符串是不可变的,它们也被“interned” - 这意味着所有已加载类中的字符串字面量都被保存在一个池中,因此通常在内存中只有每个唯一字符串字面量的一个实例。这是“享元模式”的应用,类似的池也被保留用于整数和其他基本包装对象(但仅限于少量小值)。

由于这种机制,即使来自不同类的字符串字面量,它们的身份比较通常也是可能的(尽管为了安全和一致性,您应始终使用“equals”方法来比较字符串):

System.out.println("hello" == "hello"); // true

现在,如果您使用默认的字符串构造函数,则会得到一个空字符串实例,但正如JavaDoc中所述,它是一个实例:

初始化新创建的String对象,以便它表示一个空字符序列。请注意,使用此构造函数是不必要的,因为字符串是不可变的。

这样的新实例与内部化的空字符串不同,结果是:

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

但是正如我所说,只有字符串字面量会自动合并 - 这意味着手动创建的字符串(例如通过StringBuilders、char数组等)不会被合并。您可以使用String.intern()方法手动将这样的字符串放入池中。

现在来看一个真实的场景

这一切都很好,但我仍然没有回答为什么存在这个构造函数。嗯,Java字符串只是智能包装器,可以覆盖char数组,一些不同的字符串对象可以共享它们的内部数组。

如果我创建一个非常长的字符串(例如通过读取流),那么此实例不会被合并(如上所述),因此在引用它的变量超出范围后,它将被垃圾回收。但是如果我这样做:

String longString = readVeryLongString();
String shortString = longString.subString(0, 10);

如果新的shortString不从longString中复制前10个字符并将它们放入自己的新字符数组中,那么它将引用原始数组,仅使用其中的前10个字符。

现在,如果shortString变量的生命周期更长(例如放置在某些静态上下文中),则底层的char数组将不会被垃圾回收(即使原始的longString变量已经超出了范围)。这是在Java中创建内存泄漏的方法之一。

现在,默认的字符串构造函数来拯救!如果我将上面的代码更改为:

String longString = readVeryLongString();
String shortString = new String(longString.subString(0, 10));

...然后shortString将成为一个新的字符串实例,它通过只从由subString方法返回的原始字符串中复制所需的10个字符来创建一个新的内部字符数组。


一个很好的文章阐述了这个主题:

http://illya-keeplearning.blogspot.cz/2009/03/java-string-internals.html


1
这是一个很好的解释,说明了new String(existingString)的用途,但问题实际上是关于没有参数的new String()。 :-/ - ruakh
是的,你说得对,我有点冲动了。 :) 我会尝试更新答案... - Natix

-1

要创建一个空字符串,调用默认构造函数如 String s = new String();

将创建一个没有字符的字符串实例。


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