今天偶尔看到一篇博客说Java中String为什么是不可变的(immutable),貌似分析的头头是道,还拿出来源码振振有词的说的,看,就是因为它在声明类的时候是被final修饰的,然后就长篇大论final的东西
对于这类的博客实在不知道说啥好,一瓶水不满,半瓶水晃荡。
那么问题来了,String类为什么要被设计成不可变的,是怎么样设计为不可变的?
问题一,为什么要设计成不可变的,也就是不可变的好处,这个很明确,安全性和效率是两大方面,可以参照stackoverflow上这个问题 为什么String类要被声明为final
今天我想重点说的是问题二:
String str = "hello word"; str = "ni hao"; System.out.println(str); //out:ni hao
String str = "hello word"; System.out.println(str); //这里使用hashcode替代内存地址(不严谨) System.out.println("str的地址:"+ str.hashCode()); str = "ni hao"; System.out.println("str的地址:"+str.hashCode()); System.out.println(str); //out: //hello word //str的地址:-1604693608 //str的地址:-1047734607 //ni hao
我们知道当我们创建一个String对象str时(String str = "hello word";),其在内存中的状态是这样的:
再执行str = "ni hao"时,就变成了这样的:
我们可以看到str实际上指向了一个新的地址,而不是在原内存地址上修改数据,这就是我们所说的不可变。
这个就需要读下源码了,String的源码前三行:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[];
很明显,这里就点明了String是怎么保证不变的。
String类是个final类,这就意味着String类不可被继承,不可被继承就不能带来熊孩子,在根源上防止熊孩子带来的破坏!
String类的主要字段char value数组也是被final修饰的
很多博客的分析就到这戛然而止了,但是这就完了吗?
很明显不是,value虽然是被final修饰了,但是这只是修饰了value,但是value是一个数组,这个数组是可变的如果这个数组改变了,那么string也就变了。数组的数据结构如下:
这可以看出value这个变量只不过是在栈上的一个引用,数组实际的内容是 在堆上的,使用final修饰value只能限定栈里面这个value引用地址不可以变了,不能再指向其他地址,但是不能限制堆上的内容不可以改变。 见下面这个例子:
final char[] value = {'a','b','c'}; //out:abc System.out.println(value); //error:Array initializer is not allowed here value = {'1','2', '3'}; value[0] = '1'; //out:1bc System.out.println(value);
上述代码在ide中直接会提示第3行错误,编译不会通过的,原因就是这里定义的value是final类型的,一旦初始化之后就不可以再次指向别的引用;但是也可以看到value指向的char数组是可以改变内容的,所以说String的不可变并不完全是由final修饰的value数组决定。
那是由什么决定的呢?
当然是由这个类代码的作者决定的,通读String类的源码的话,会发现后面所有String的方法都很小心的没有去修改这个数组的内容,也没有对外暴露成员,设置为private类型,再加上String类不可以被继承,避免被其他继承类修改,这样就完美实现了String的不可变。
好了,这里留下一个悬念,也是我当初在阅读String源码时候不解了好几天的地方,String构造方法中是这样的:
/** * Initializes a newly created {@code String} object so that it represents * the same sequence of characters as the argument; in other words, the * newly created string is a copy of the argument string. Unless an * explicit copy of {@code original} is needed, use of this constructor is * unnecessary since Strings are immutable. * * @param original * A {@code String} */ public String(String original) { this.value = original.value; this.hash = original.hash; }
问题是String类中的value属性是被定义为private类型,那么这里为什么可以访问到original的value值的呢?
我当时可是郁闷了好一阵。。。。