先了解一些背景
因为 Java 中的字符串是不可变的,所以它们也是“内部的”——这意味着加载的类中的所有 字符串文字 都保存在一个池中,因此每个类通常只有一个实例一次在内存中唯一的字符串文字。它是 flyweight 模式的应用,类似的池也用于 Integer 和其他原始包装对象(但仅限于有限数量的小值)。
由于这种机制,通常可以对字符串文字(甚至来自不同类)进行身份比较(尽管在比较字符串时应始终使用equals 方法以确保安全性和一致性):
System.out.println("hello" == "hello"); // true
现在,如果您使用默认的字符串构造函数,您将获得一个空字符串的实例,但它是一个 new 实例,如 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