在Java中String是一个不可变类。不可变类是一个简单的类,它的实例不能被修改。创建实例的时候在一个实例中的所有信息被初始化并且信息不能被修改。不可变类有许多的优点。本文总结了为什么字符串被设计成不可变的。一个很好的答案是:取决于对内存,同步,数据结构等的深刻理解。

1.字符串池的要求

String pool (String intern pool)在方法区是一种特殊的存储区域。当创建一个字符串,如果字符串已经存在于池中,现有字符串的引用将被退回,而不是创建一个新的对象,并返回它的引用。

下面的代码将只创建一个字符串堆对象。

1
2
String string1 = "abcd";
String string2 = "abcd";

下图是它的运作方式:

java 字符串池

如果字符串不是一成不变的,不断变化的字符串与一个参考值将导致对其他引用得到了错误的值。

2.缓存Hashcode

在java中是经常使用hashcode的。例如,在一个HashMap中。不可改变保证了哈希码总是相同的,所以,它可以无需担心改变。这意味着,没有必要每次使都计算哈希码。这是更有效的方式。

在String 类中, 它包括了下面的代码:

1
private int hash;//这是用来缓存哈希值的。

3.协助其他对象的使用

为了使这个问题更加具体,看一下下面的代码:

1
2
3
4
5
6
7
HashSet<String> set = new HashSet<String>();
set.add(new String("a"));
set.add(new String("b"));
set.add(new String("c"));
for(String a: set)
a.value = "a";

在这个例子中,如果字符串是可变的,它的值是可以改变这将违反set的设计(set集包含不重复的元素)。这个例子的目的是为了简单起见,在实际中String类是没有value 的域。

4. 安全问题

在java类中,字符串是被广泛地作为用参数,例如:网络连接,打开文件等等。如果字符串不是不变的,连接或文件将被改变,并导致严重的安全威胁。该方法还以为是连接到同一台机器,但实际上市没有。可变字符串可能会导致反射的安全问题也一样,这些参数都是字符串。

下面是例子代码:

1
2
3
4
5
6
7
boolean connect(string s){
if (!isSecure(s)) {
throw new SecurityException();
}
//这将引起问题, 如果s是可变的,别处s的引用改变s的值。
causeProblem(s);
}

5. 不可变的对象通常是线程安全的

由于不可变对象不能改变,就可以在多个线程之间自由共享。这消除了执行同步的要求。

总之,字符串被设计为不可变的,为了效率和安全性。这也是为什么一般情况下不可变的类是优选的。

延伸阅读

参考文档