1.什么是不可变类型
不可变对象是指一个对象的状态在对象被创建之后就不再变化。这里的不可变化是指不可以修改这个类的内容,这样的设计有很多的好处,不可变的对象可以复用,是共享的,同时还设计到了一个线程安全的问题,不可变类的不变性确保了多个线程在访问同一个对象的时候,是线程安全的。这里有兴趣的可以看一下《Effective Java》这本书,很经典的书,一般别人问我推荐什么java程序员必读的书,这本书是我第二推荐的书,当然,第一本还是《颈椎病康复指南》。
那么对于String对象来说,怎么解释String是不可变的呢,一句话,不能改变一个字符串的值。这句话在很多初学者看来非常的难以理解。我不是经常进行字符串拼接的吗,为什么说没有改变它的值呢?写个demo来看一下。
首先创建了一个字符串“love”,然后进行拼接,显然第一次输出的是love,第二次输出的是loveyou。看上去似乎改变了str的内容,其实这只是表象,我们来进行debug一下。
我们知道String的底层实现是一个数组(下面会讲),字符串为love时,str指向的是@434,进行了字符串凭借之后,指向了@437,并不是一样的,也就是说这里的str其实指向了另一个地址对象而已,也就是每次都生成了新的对象(这里的说法存在一些的问题,结合一下String的堆栈常量池相关的内容进行理解)那么之前指向的love在哪里,答案是在常量池里面。这部分内容以后抽时间写一篇说一下,其实概括一下就是,如果是编译期已经创建好(直接用双引号定义的)的就存储在常量池中进行维护。
2.Stirng如何实现不可变
直接看一下String的源码
可以看到,String底层是用一个不可变的数组进行存储,final修饰的字段创建以后是不可改变的。这时候思考一下,final修饰的意义,它表示这个数组的引用地址是不可改变的。但同时我们知道,地址虽然不可以改变,但是我们可以改变数组上的值啊---value[]的地址唯一,但是value[i]对应的的值我们是可以改变的。这里就是String不可变的关键,写源码的这些牛逼人物保证了这些数组的值不会对外暴露,private保证了其私有的属性,同时String整个类也是final的,防止继承之后进行修改。
3.为什么要设计成不可变的类型
首先我们应该思考的是,既然有不可变,那么有可变String类使用吗。牛逼的源码工程师肯定考虑到了,StringBuffer和StringBuild这两个类是对本身字符串进行修改操作,至于这两个的区别,这里暂时不做讨论。
关于设计成不可变类的好处有很多的方面,我考虑的主要是常量池维护以及安全。首先String是维护在常量池里面的,所以不同的client都可以共享这里的数据,共享这种行为本身就是非常不安全的,所以我们必须保证这里面的数据不能够被随意的更改,因为更改了其中的一个数据,必然会对其他引用数据的客户端造成不可预估的危害。另一个方面,对于hashmap的影响,我们在使用hashmap的时候,有时候key值的类型是String类型的,我们知道hashmap是严格控制key值不唯一的,这里的string若是可变,同样会引发不可预估的错误,例如对应的value值丢失。(感谢国外code Javin Paul的文章,原文地址http://javarevisited.blogspot.hk/2010/10/why-string-is-immutable-or-final-in-java.html,英文需翻墙)
其实好处是很多方面的,相信造轮子的这些人也是考虑了方方面面,有其他的一些思考可以在底下留言讨论,文章中若有错误的地方,希望指出,我会修改以免造成误导。