转载

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

引子

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

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

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

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

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

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

String是怎么设计为不可变的

1. 不可变的是什么?

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

        String str = "hello word";
        str = "ni hao";
        System.out.println(str);
           //out:ni hao

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

        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

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

我们知道当我们创建一个String对象str时(String str = "hello word";),其在内存中的状态是这样的:

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

再执行str = "ni hao"时,就变成了这样的:

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

我们可以看到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也就变了。数组的数据结构如下:

由一篇博客引发的对 Java 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值的呢?

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

原文  https://mp.weixin.qq.com/s/m4K28KP65GsBeUUaVtRgMg
正文到此结束
Loading...