本文转载自微信公众号《飞天小牛》,作者飞天小牛。转载本文请联系飞天小牛公众号。为什么String是不可变的?因为String中的char数组是final修改的。我相信你已经把这组答案背得很烂了,但这是不正确的!面试官:说说String、StringBuilder、StringBuffer的区别:String是不可变的,而StringBuilder和StringBuffer是可变的,等等等等……面试官:为什么String是不可变的?我:String是final修饰的,也就是说不能继承String;而String中实际存放字符的地方是一个char数组,被final修饰,所以String是不可变的面试官:String是不可变的真的是因为final吗?我:是的……是的,面试官:好的,你有什么问题吗?我:典当……什么是不可变的??中不可变对象(ImmutableObject)的定义是:一旦对象被创建,对象的所有状态和属性在其生命周期内都不会改变。这意味着一旦我们将一个对象赋值给一个变量,该对象的状态就不能以任何方式改变。String不可变的表现是,当我们试图将值“abcde”赋值给一个已经存在的对象“abcd”时,String会创建一个新的对象:为什么String是不可变的?String用final修饰char数组,这个数组是不能修改的,所以说没有错。但!!!这个不可修改只是引用地址不可修改(也就是说栈中调用value的引用地址是不可变的,编译器不允许我们将value指向堆中的另一个地址),不是表示存放在堆中的数组本身的内容是不可变的。例如:如果我们直接修改数组中的元素,完全OK:既然说String是不可变的,那么仅仅依赖final显然是不够的:1)首先,char数组是private的,而String类没有提供修改这个数组的外部方法,所以初始化后没有有效的手段去改变它;2)其次,String类被final修饰,即不能被继承,避免被别人继承后被销毁;3)终于重要了!因为Java作者在String的所有方法中都小心翼翼地避免修改char数组中的数据,所有涉及修改char数组中数据的操作都会重新创建一个String对象。可以查看源码来验证这个说法,比如substring方法:为什么要设计成不可变的?1)首先,需要一个字符串常量池。我们再回顾一下字符串常量池的定义:频繁创建大量的字符串会极大地影响程序的性能。为此,为了提高性能,减少内存开销,JVM在实例化字符串常量的时候做了一些优化:为字符串开辟了一个字符串常量池StringPool。可以这样理解,在为缓存创建字符串常量时,首先检查该字符串是否存在于字符串常量池中。如果该字符串存在于字符串常量池中,则直接返回引用实例,无需重新实例化;如果不存在,则将字符串实例化并放入池中。如下代码所示,堆内存中只会创建一个String对象:Stringstr1="hello";Stringstr2="hello";System.out.println(str1==str2)//true假设允许String被改变了,那么如果我们修改str2的内容就好了,那么str1也会被修改,显然这不是我们想要看到的结果。2)还有一点比较容易想到,String为了安全被设计成不可变的。String作为最基本、最常用的数据类型,被很多Java类库用作参数。如果String不固定,会造成各种安全隐患。举个例子,我们看一下将可变字符串StringBuilder存储到HashSet中的场景:我们将可变字符串s3指向s1的地址,然后改变s3的值,因为StringBuilder并没有设计成像不可变的String所以s3会直接修改s1的地址,导致s1的值改变。于是,坏事发生了,HashSet中出现了两个相等的元素,打破了HashSet不包含重复元素的原则。另外,在多线程环境下,众所周知,多个线程同时修改同一个资源是很危险的,但是String作为不可变对象是不能修改的,多个线程是绝对不可能的线程同时读取同一个资源的问题,所以String是线程安全的。String真的是不可变的吗?改变String无非就是改变char数组value的内容,而value是私有属性,那么Java中有没有办法访问类的私有属性呢?对,就是反射,使用Reflection可以直接修改char数组的内容,当然一般来说,我们不会这样做。看下面的代码:综上所述,并不是因为char数组是final的所以String是不可变的,而是为了设计String不可变而将char数组设置为final的。这里有一些创建不可变对象的简单策略。当然,并不是所有的不可变类都完全遵循这些规则:不提供setter方法(包括修改字段的方法和修改字段引用的对象的方法);定义类的所有字段都是final和private的;不允许子类重写该方法。简单的方法是将类声明为final,更好的方法是将构造函数声明为private,通过工厂方法创建对象;如果类的字段是对可变对象的引用,则不允许修改引用的对象。
