Java中使用String(String)构造函数的方法

11

我读过一些文章和书籍,它们都说应该尽可能避免使用String s = new String("...");。我明白为什么要这样做,但是是否有使用String(String)构造函数的情况呢?我认为没有,也没有看到任何证据表明有用处,但我想知道SO社区是否有人知道。

6个回答

42

这是一篇好文章: String构造函数被认为是无用的,结果证明仍然有用!

It turns out that this constructor can actually be useful in at least one circumstance. If you've ever peeked at the String source code, you'll have seen that it doesn't just have fields for the char array value and the count of characters, but also for the offset to the beginning of the String. This is so that Strings can share the char array value with other Strings, usually results from calling one of the substring() methods. Java was famously chastised for this in jwz' Java rant from years back:

The only reason for this overhead is so that String.substring() can return strings which share the same value array. Doing this at the cost of adding 8 bytes to each and every String object is not a net savings...

Byte savings aside, if you have some code like this:

// imagine a multi-megabyte string here  
String s = "0123456789012345678901234567890123456789";  
String s2 = s.substring(0, 1);  
s = null;

You'll now have a String s2 which, although it seems to be a one-character string, holds a reference to the gigantic char array created in the String s. This means the array won't be garbage collected, even though we've explicitly nulled out the String s!

The fix for this is to use our previously mentioned "useless" String constructor like this:

String s2 = new String(s.substring(0, 1));  

It's not well-known that this constructor actually copies that old contents to a new array if the old array is larger than the count of characters in the string. This means the old String contents will be garbage collected as intended. Happy happy joy joy.

最后,Kat Marsen 提出以下几点:

首先,字符串常量永远不会被垃圾回收。其次,字符串常量会被intern'd,这意味着它们在整个VM中是共享的。这可以节省内存。但这并不总是你想要的。

String上的复制构造函数允许您从字符串字面值创建一个私有的String实例。这对于构建有意义的互斥对象(用于同步)非常有价值。


9
请注意,自Java 7(我想是)以来,String.substring()的实现已更改以避免此问题,因此此内容已不再相关。 - Jesper
很抱歉我要对这个技术上准确且经过深入研究的答案进行负评,但时间已经让它变得不那么相关了(基本上只有历史意义,不再适用于当前的JVM)。 - Joachim Sauer

11

如果我没记错的话,唯一“有用”的情况是原始字符串是更大的后备数组的一部分。(例如作为子字符串创建)。如果你只想保留小的子字符串并回收大的缓冲区,那么创建一个新的字符串可能是有意义的。


“substring”或者你用来截取它的任何东西难道不已经创建了新的字符串吗? - Paul Tomblin
1
不,substring 保留了对原始 char[] 的指针,而没有创建新的 char[](这样更快,但可能会导致内存泄漏)。 - Michael Myers
1
无论如何,你并不总是控制创造字符串的方式 - 查看我的答案以获取一个现实世界的例子。 - Jon Skeet
更好的(在我看来)字符串实现会去掉偏移量和长度。不过,你可能仍然需要处理一些不太好的实现。 - Tom Hawtin - tackline

8

仅仅是为了扩展Douglas的答案*,我可以给出一个明确的例子,说明我在哪里使用过它。考虑读取一个包含成千上万行单词的字典文件,每一行只有一个单词。最简单的读取方法是使用BufferedReader.readLine()

不幸的是,readLine()默认会分配一个80个字符的缓冲区作为预期行长。这意味着通常它可以避免无意义的复制——浪费一点内存通常也不太糟糕。然而,如果你正在为应用程序的持续时间加载短单词的字典,你最终会有大量的内存永久性地被浪费掉。我的“小应用程序”消耗了比它应该消耗的更多的内存。

解决方案是改变这个:

String word = reader.readLine();

转换成这样:

String word = new String(reader.readLine());

当然要带上注释!

* 我不记得我是否与道格拉斯一起工作时遇到了这个问题,或者他只是偶然回答了这个特定的问题。


0

构造函数在一般情况下很多余,不建议使用。带有字符串参数的String构造函数很少使用,除非要创建现有字符串变量的独立副本。基本上只用它来“澄清”你的代码。没有更少。


0

来自javadoc

/**
 * Initializes a newly created {@code String} object so that it represents
 * the same sequence of characters as the argument; in other words, the
 * newly created string is a copy of the argument string. Unless an
 * explicit copy of {@code original} is needed, use of this constructor is
 * unnecessary since Strings are immutable.
 *
 * @param  original
 *         A {@code String}
 */
public String(String original){.....}

-2

如果你不想给原始字符串一个句柄,那么可以使用它来获取原始字符串的副本。但是,由于字符串是不可变的,所以我不认为存在使用此功能的情况;)


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