27
2017
09

Java为什么把String设计成不可变的?

原文来自:Why String is immutable in Java?

这是来自Java和Android面试中经常被问的一道题目。在Java语言中,String是字符串常量,StringBuilder是字符串变量,为什么将String设计成不可变的(immutable)?

在Java里String类是不可变的,不可变类是一个不能被修改实例的类,实例创建时所有的信息都被初始化,并且不可被修改。这样的设计也有很多优点,本文从内存、同步和数据结构方面总结了为什么把String类设计成不可变的原因。

1、字符串常量池的需要

字符串常量池是一个特殊的存储区域。当创建字符串时,如果字符串已经存在于池中,则将返回现有字符串的引用,而不会创建新的对象。
下面的代码只在堆中创建一个字符串对象:

String string1 = "abcd";
String string2 = "abcd";

示意图如下:
这里写图片描述
如果String是可变的,用一个引用改变字符串将导致其他引用的值错误。

2、HashCode缓存

在Java里存储一个字符串通常需要使用到HashCode值,例如,在一个HashMap或一个HashSet。一成不变可以保证HashCode的唯一性,不用考虑他的变化。这意味着不需要每次使用它的时候都计算HashCode,提高操作效率。
在String类中有如下代码:

private int hash;//this is used to cache hash code.

3、促进使用其他对象

下面的程序我们具体说明:

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";

在这个实例中,如果字符串是可变的,他将违背集合的设计(集合包含非重复元素)。当然,上面的示例仅仅用作演示用,在真正的字符串类中没有值字段。

4、安全

字符串被广泛用于许多Java类的参数,比如网络连接、打开文件等等。如果字符串不是不可变的,连接或者文件的更改将会导致严重的安全威胁。他认为连接到一个机器,并不是。因为参数是字符串的,字符串可变会导致在反射中的安全问题。(不可变性使之能够在不同的线程间共享,同时确保线程安全,帮助使用者减少线程同步的开发工作)
示例如下:

boolean connect(string s){
    if (!isSecure(s)) { 
        throw new SecurityException(); 
    }
    //here will cause problem, if s is changed before this by using other references.    
    causeProblem(s);
}

5、不可变对象自然是线程安全的

因为不可变对象是不能被改变的,所以他们可以自由的共享多线程。这就消除了他们进行同步的要求。

总结

总之,String被设计成不可变的是出于效率和安全的因素。这也是为什么很多情况下首选类是不可变类的原因。

当然,不同的使用场景体现出不可变特性的优势也不相同。大家可以回忆自己项目中的使用场景,多加思考这类问题。

上一篇:Code : delegate,event,lambda 下一篇:Android使用UIL加载圆角图片不显示的问题及解决办法