为什么Java里面String类是不可变的

在Java里面String类型是不可变对象,这一点毫无疑问,那么为什么Java语言的设计者要把String类型设计成不可变对象呢?这是一个值得思考的问题。

Java语言的创建者James Gosling,曾经在一次采访中被人问到:什么时候应该使用不可变对象(immutable object),他回答:任何可以使用的时候都会使用。

在这之前,我们先来简单了解一下,什么是不可变对象?

不可变对象指的是在对象创建之后,对象的内部状态以及对象的内存指针地址都不不能被改变。在Java里面final关键字就是用来辅助创建不可变对象的,但需要注意的是,对于基本类型被final修饰后,就彻底变成了不可变对象,而引用类型被final修饰后,仅仅是指针的内存地址不能改变,如果想要变成彻底的不可变类型,要把该对象里面所有的字段都得用final声明,包括嵌套的对象,否则对象的内部状态也是会变化的,这一点需要理解。

ok,下面我们来分析下为什么String是不可变的?

通过String源码可以看到,String类型的底层是由final修饰的char数组存储。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    ........
    }

String能被设计成不可变类型的一个重要前是因为它是编程语言里面使用频率最高的一种类型。不可变类型带来的好处,体现在四个方面,分别是:缓存,安全,同步和性能。

(一)缓存

在JVM的运行时数据区域里面,有一个专门的字符串常量池用来存储字符串字面量,如下面一段代码:

String s1 = "Hello World";
String s2 = "Hello World";

assertThat(s1 == s2).isTrue();

s1和s2变量指针的内存地址其实是一样的,也就是说他们代表是同一个对象,这是jvm常量池做的优化,当第一个字面量声明的时候,它的值会被字符串常量池存储,当s2变量声明的时候,jvm发现常量池已经存在该对象,所以就不会再创建一次,而是直接将一样的内存指针赋值给s2变量,从避免了重复创建对象,节省了内存空间。

此外,由于字符串的不可变性,从而可以让其hashCode也被缓存,在Java里面哈希类数据结构如HashMap, HashTable, HashSet其key用的最多的基本都是String类型,如此一来key的hashCode的也可以在第一次调用之后被缓存,之后直接使用无须重新生成,从而间接的提升访问效率。

(二)安全

不可变特性也能够减少了应用程序在运行时间的安全问题,如下面的一段代码:

void criticalMethod(String userName) {
    // check
    if (!check(userName)) {
        throw new SecurityException(); 
    }

    // query 
    query(userName);

    }

在上面的一段代码,在调用这个方法之后,先检查用户名,如果合法才可以继续查询相关数据,如果String可变,那么攻击者就可以在通过check验证之后,再改变查询的用户名,那么就会存在安全风险,而不可变性能够避免和减少这一情况。另一方面,如果String是可变的,那么同时运行的其他线程如果修改这个值,就有可能导致混乱。

(三)同步

由于String类型的不可变性,使得String对象可以安全的在多个线程之间传递和访问,也就是说你在多线程中是不能改变字符串本身的值,而是在堆里面新创建一个字符串然后操作。当然如果没有final修饰,你是可以改变这个变量的引用地址,也就是说你可以把新生成的内存引用覆盖原来的变量引用,但这里仅仅是引用,并不是变量的值。这一点要注意。

(四)性能

性能方面,其实前面已经提到了,比如字符串的常量池节省内存,缓存Hash类以字符串做key数据结构的hashCode,从而提高访问性能等。由于字符串是编程语言里面最广泛使用的数据结构,所以针对字符串的不可变性带来的优势,可以放大到整个运行的应用程序,从而带来应用程序整体的性能提升。

总结:

本文主要介绍了Java语言里面String类型为什么设计成不可变类型,以及分析了不可变类型的带来的主要优势,需要注意的是虽然不可变类型能够带来不少的好处,但并不是说其没有弊端,不可变类型的每一次修改都需要在内存中新生成一个对象,从另一个方面说针对经常变化的对象是不适合使用不可变类型的,这也是为什么Java里面还提供了可修改值的StringBuilder和StringBuffer类,这在实际开发中常常是需要根据具体情况权衡的。

原文发布于微信公众号 - 我是攻城师(woshigcs)

原文发表时间:2019-01-06

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区