Java中“ new String(…)”表达式的目的是什么?


75

在查看在线代码示例时,有时会遇到通过使用new运算符将String常量分配给String对象的情况。

例如:

String s;
...
s = new String("Hello World");

当然,与

s = "Hello World";

我不熟悉这种语法,也不知道其目的或效果。由于String常量通常存储在常量池中,然后JVM以任何表示形式存储在处理String常量中,因此什至会在堆上分配任何东西吗?


1
看一下这篇博客文章。kjetilod.blogspot.com/2008/09/…–
Ruggs

@Ruggs很好,谢谢您的链接,但是如果您像这个家伙一样添加了有关警告的免责声明,那将很好。


2
不管关于如何/为什么new String(String)使用的答案,s = new String("Hello World")参数是文字的情况在Java中都没有意义,而且可能永远不会。
Maarten Bodewes

Answers:


80

在一个地方,你可能会认为你想new String(String)是迫使内部字符数组的一个独特的副本,如

small=new String(huge.substring(10,20))

但是,不幸的是,此行为是未记录的,并且取决于实现。

当将大文件(某些文件最多可达20 MiB)读入字符串并在事后将其刻成几行时,我对此感到非常恼火。我最后得到了引用由整个文件组成的char []的所有字符串。不幸的是,与处理文件相比,我无意间保留了对整个数组的引用,而保留的时间却比处理文件更长new String()。由于处理20,000个文件很快就消耗了大量的RAM,所以我不得不使用它来解决该问题。

唯一与实现无关的方法是:

small=new String(huge.substring(10,20).toCharArray());

不幸的是,这必须将数组复制两次,一次toCharArray()并在String构造函数中一次。

需要有一种通过复制现有字符串的字符来获取新String的文档化方法;或String(String)需要改进的文档以使其更明确(此处有隐含含义,但它相当模糊且易于解释)。

假设文档没有陈述的陷阱

为了回应不断出现的评论,请观察Apache Harmony的实现new String()是:

public String(String string) {
    value = string.value;
    offset = string.offset;
    count = string.count;
}

没错,那里没有底层数组的副本。但是,它仍然符合(Java 7)String文档,因为它:

初始化一个新创建的String对象,使其表示与参数相同的字符序列;换句话说,新创建的字符串是参数字符串的副本。除非需要显式的原始副本,否则不需要使用此构造函数,因为字符串是不可变的。

突出的部分是“参数字符串的副本”;它不会说“参数字符串和支持该字符串的基础字符数组的副本”。

请注意,您应按照文档而不是一种 实现编程。


“但是,不幸的是,这种行为是无证的,取决于实现。” String(String)构造函数的JavaDoc说:“初始化新创建的String对象,使其表示与参数相同的字符序列;换句话说,新创建的字符串是参数字符串的副本。除非需要显式的原始副本,否则,由于字符串是不可变的,因此不需要使用此构造函数。”这是一种回旋方式,可以说所述构造函数char[]String传递给它的底层进行了显式复制。
Powerlord 2010年

7
@R。Bemrose。不,这可能意味着。它声明的是您将获得String对象的新副本-它不声明该对象的组成内容。共享基础数组的新String仍然是新String和旧副本的副本。将String(char[] value)其与明确地指出:进行比较:复制字符数组的内容。
劳伦斯·多尔

1
@Monkey我不同意您对Javadoc的解释。我们可以在此处利用布尔方程式:“除非需要原始的显式副本,否则不需要使用此构造函数”将转换为!explicitCopyRequired --> constructor is unnecessary(->含义为'隐含')和规则a --> b<==>!b --> !a来揭示constructor is necessary --> explicitCopyRequired。如果使用构造函数,那是因为您需要显式副本。现在,我同意用更好的措词,但是当您分解它时,很显然此构造函数通过合同创建了一个显式副本。
corsiKa 2011年

2
@Glowcoder-进行所需的所有推断,这不是JavaDoc所说的。而且IIRC至少实现了一个主要的JVM实现new String(String)而没有复制基础字符数组-可能是Apache Harmony,但我不确定。
劳伦斯·多尔

9
请注意,在最新版本的Oracle JVM中,子字符串按照此答案中描述的方式共享基础数组substring而是将数据复制到新数组。这始于Java 1.7更新6。请参见此处
Lii

10

我唯一发现这有用的是声明锁变量:

private final String lock = new String("Database lock");

....

synchronized(lock)
{
    // do something
}

在这种情况下,诸如Eclipse之类的调试工具将在列出线程当前持有或正在等待的锁定时显示该字符串。您必须使用“新字符串”,即分配一个新的字符串对象,因为否则共享字符串文字可能会锁定在其他一些不相关的代码中。


1
我认为使用use更好private static class Lock {}; private final Lock lock = new Lock();,因为类名几乎随处可见。但是,由于HotSPot效率不高,因此将花费您数K的费用。
Tom Hawtin-大头钉

我可以买。也许我下次再试。
戴夫·雷

12
对象锁= new Object()完全相同;-)
硬编码

@Hardcoded对,除非不是Serializable,所以我更喜欢new Object[0]
maaartinus

3
@maaartinus序列化同步监视器有什么意义?实例之间无法同步,因此毫无意义。相反,我的显示器始终是最终的。
硬编码

4

Software Monkey和Ruggs描述的该构造函数的唯一实用工具似乎已从JDK7中消失。offset类String中不再有字段,并且子字符串始终使用

Arrays.copyOfRange(char[] original, int from, int to) 

修剪副本的char数组。


2
在此之前,除非有文档证明这样做,否则您仍然依赖于实现的副作用,而不是记录的操作。仅仅因为JDK7恰好在其中复制了阵列substring()并不意味着它必须在每个实现中都必须这样做(而且OpenJDK并不是唯一的JVM实现)。
劳伦斯·多尔

确实,但是这里描述的问题是特定实现的副作用,而不是在任何地方指定或记录的问题。所以这很重要。
MasterKiller

4

字符串s1 =“ foo”; 文字将放在StringPool中,而s1将被引用。

字符串s2 =“ foo”; 这次它将检查StringPool中是否已经有“ foo”文字,因为它现在已经存在,所以s2将引用相同的文字。

字符串s3 = new字符串(“ foo”); 首先在StringPool中创建“ foo”文字,然后通过字符串arg构造函数创建String Object,即由于通过新运算符创建对象而在堆中创建“ foo”,然后s3将引用它。

字符串s4 = new字符串(“ foo”); 与s3相同

所以System.out.println(s1 == s2); //由于文字比较而为true

System.out.println(s3 == s4); //由于对象比较而为false(在堆中的不同位置创建了s3和s4)


4
不知道为什么会投票赞成……它没有以任何方式回答问题。
Christopher Schneider

我发现它很有帮助,因为它回答了一个我找不到的问题:“使用构造函数创建的字符串是否跳过
内部调试

2

好吧,这取决于示例中的“ ...”。例如,如果它是一个StringBuffer或一个字节数组或其他东西,您将获得一个由传递的数据构造的String。

但是,如果它只是另一个String,如中所示new String("Hello World!"),则"Hello World!"在所有情况下都应将其替换为simple 。字符串是不可变的,因此克隆一个字符串毫无用处-创建一个新的String对象只是作为现有String的副本(无论是文字变量还是您已经拥有的另一个String变量),它的冗长且效率较低。

实际上,Effective Java(我强烈建议)将其完全用作其“避免创建不必要的对象”的示例之一:


作为不执行操作的极端示例,请考虑以下语句:

String s = new String("stringette");  **//DON'T DO THIS!**

(有效的Java,第二版)


2
仅仅因为您使用包含字符串的重载并不意味着它没有意义-请参阅Software Monkey的答案。
乔恩·斯基特

0

以下是《有效的Java第三版》(第17项:最小化可变性)一书的引文:

可以自由共享不可变对象的事实的结果是,您不必为它们制作防御性副本(第50项)。实际上,您根本不必制作任何副本,因为这些副本将永远等同于原始副本。因此,您不需要也不应该在不可变类上提供克隆方法或副本构造函数(第13项)。在Java平台的早期,这还没有得到很好的理解,因此String类确实具有复制构造函数,但是很少使用(如果有的话)。

所以,这是由Java的一个错误的决定,因为String类是不可变的,他们不应该提供拷贝构造函数这个类中,你想要做的不变类昂贵的操作情况下,你可以使用的是公共可变伴侣类StringBuilderStringBuffer在情况String


-1

通常,这表明有人对初始化时声明的新型C ++样式不满意。

早在C天之前,在内部范围内定义自动变量并不是一种好方法。C ++消除了解析器限制,Java对此进行了扩展。

所以你看到的代码有

int q;
for(q=0;q<MAX;q++){
    String s;
    int ix;
    // other stuff
    s = new String("Hello, there!");
    // do something with s
}

在极端情况下,所有声明都可能在函数的顶部,而不是在for此处的循环之类的封闭范围内。

但是,总的来说,这样做的结果是导致String ctor被调用一次,并丢弃生成的String。(避免这种情况的愿望正是导致Stroustrup允许在代码中的任何位置进行声明的原因。)因此,您是正确的,它充其量是不必要的,不好的样式,甚至可能实际上是不好的。


4
我可能完全不理解这一点,或者与实际问题无关。问题是关于... =“ Hello”和... = new String(“ Hello”)之间的区别。您似乎在谈论“ String s = ...”和“ String s; ...; s = ...”之间的区别。
戴夫·科斯塔

而且我不得不衷心地不同意“至少在1991年的C天以来,“在C天之内,在内部范围内定义自动变量并不是一种好形式”;根据我的经验,始终认为最好在C中声明变量的范围应尽可能小于编译器所允许的范围,自1991年以来,它至少一直位于任何块的顶部。
劳伦斯·多尔

-1

用Java创建字符串的方式有两种。以下是这两种方法的示例:1)声明类型为String(Java中的类)的变量,并将其分配给应放在双引号之间的值。这将在内存的字符串池区域中创建一个字符串。例如:String str =“ JAVA”;

2)使用String类的构造函数并传递一个字符串(双引号内)作为参数。例如:String s = new String(“ JAVA”); 这将在主内存中以及在字符串池中创建一个新的字符串JAVA(如果该字符串尚未出现在字符串池中)。


再有就是substringtrimreplacereplaceAllStringBuilderStringBuffer,等
劳伦斯·多尔

-2

我想这将取决于您看到的代码示例。

在代码示例中,大多数时候使用类构造函数“ new String()”只是为了显示一个众所周知的java类,而不是创建一个新的java类。

您应该避免大多数时候使用它。不仅因为字符串文字是intern,而且主要是因为字符串是不可变的。具有代表同一对象的两个副本没有任何意义。

尽管Ruggs提到的该文章“很有趣”,但除非有非常特殊的情况,否则不应使用该文章,因为它可能造成大于实际的损害。您将编码为实现而不是规范,并且相同的代码在JRockit,IBM VM或其他产品中无法运行相同的代码。


1
如果要丢掉其中的两个,那是有意义的,而且要比另一个大得多……
Jon Skeet

1
如果一个大于另一个,它们将是两个不同的对象,不是吗?:-/
OscarRyz
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.