由一篇博客引发的对Java String类的思考

mac2024-04-13  36

引子

今天偶尔看到一篇博客说Java中String为什么是不可变的(immutable),貌似分析的头头是道,还拿出来源码振振有词的说的,看,就是因为它在声明类的时候是被final修饰的,然后就长篇大论final的东西

对于这类的博客实在不知道说啥好,一瓶水不满,半瓶水晃荡。

那么问题来了,String类为什么要被设计成不可变的,是怎么样设计为不可变的?

问题一,为什么要设计成不可变的,也就是不可变的好处,这个很明确,安全性和效率是两大方面,可以参照stackoverflow上这个问题为什么String类要被声明为final

今天我想重点说的是问题二:

String是怎么设计为不可变的

1. 不可变的是什么?

 

       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实际上指向了一个新的地址,而不是在原内存地址上修改数据,这就是我们所说的不可变。

2. String是怎么保证不可变的

这个就需要读下源码了,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值的呢?

我当时可是郁闷了好一阵。。。。

欢迎关注公众号: LearnMoreTogether

最新回复(0)