当前位置 博文首页 > 程序猿DD:聊一聊Java字符串的不可变
点击蓝色“程序猿DD”关注我
回复“资源”获取独家整理的学习资料!
在 Java 开发中 String (字符串)对象是我们使用最频繁的对象,也是很重要的对象。正是使用得如此频繁,String 在实现层面上不断进行优化,从 Java6 到 Java7,再到 Java9 的新实现 ,都是为了提升 String 对象的性能,而其中不变的是 String 所生俱来的特性:不可变。本文主要聊一聊 String 的不可变,以及为什么存在的。
首先我们先来看下什么是不可变对象:一旦对象被创建并初始化后,内部的状态数据就会保持不变。查看 JDK 源码中的 String 类,可以看到类本身被 final 修饰,并且内部的大部分属性都是 final 修饰的,除了字段 hash 是通过字符串内容计算并缓存起来的。这样的行为让 String 类无法被扩展,内部属性也无法被修改。
接着我们再来用画图的形式来说明下 String 的不可变性。
通常我们初始化字符串都是以下形式:
String 类型的引用变量?a
?保留了一个字符串对象?string
?的引用,就如同下图所示,箭头则表示了变量?a
?与真正 String 对象的引用关系。
再通过上述代码,我们将变量?a
?赋值给变量?b
?,变量?b
?也存储了字符串对象?string
的引用,它们指向的是同一个对象。
当我们尝试对变量?a
?重新赋值,看下对变量?b
?会不会有影响呢
想必小伙伴一看就知道,打印的结果肯定是?string2,string
(图片有误,应该是a=string2)同样用画图的方式展示这两个变量与字符串对象的引用关系。
将变量?a
?重新赋值后,保存了新的引用,而不是直接在原有的字符串对象上进行数据改变,同时变量?b
?仍然存的是对象?string
?的引用,变量?a
?和?b
?两者相互独立,不影响,这也正是说明了 String 对象的不可变。
在这里初认 Java 的小伙伴还可能会有些困惑:对一个String对象?a
?赋值?string
,然后又让?a
?值为?string2
,这个时候a的值变成 了string2
,?a
?的值改变了,为什么还说 String 对象不可变呢。
其实问题也很简单,这里的?a
?只是存储 String 对象的引用,并不是对象本身,a
?存储的是指向对象所在内存的地址引用罢了,当第二次赋值时,a
?引用指向了对象?string2
的内存地址,而对象?string2
?是重新创建的,之前的?string
?对象仍在内存中,并且由变量?b
?引用着。
除此之外,String 类的返回 String 对象的方法不会改变自身,都是返回一个新的 String 对象来实现,比如?concat
,replace
,substring
?等等。
聊完什么是 String 的不可变后,接下来我们再说说 String 为什么需要不可变呢,又有什么好处呢?
在Java中,我们通常有两种方式创建字符串对象,一种是通过字符串字面量方式创建,就如上文的代码,另外一种就是通过 new 方式去创建,如?String c = new String("string 3");
?而两者区别就在于通过字符串字面量的方式创建时,JVM 会现在字符串池中检查字符串内容是否已经存在,如果存在就会直接返回对应的引用,而不是再次分配内存进行创建,如果不存在就会分配在内存中创建的同时将字符串数据缓存在字符串池中,便于重用。正是是由于字符串的不可变,同样的字符串内容可以让 JVM 可以减少额外的内存分配操作,直接使用在字符串池中字符串对象即可,对性能提升和内存节省都大有好处。
关于字符串池,这里稍微简单介绍一下:Java 的字符串池属于 JVM 专门给指定的特殊内存区域,用来存储字符串字面量。在 Java 7 之前,分配于 JVM 的方法区内,属于常量池的一部分;而 Java7 之后字符串池被移至堆内存进行管理,这样的好处就是允许被 JVM 进行垃圾回收操作,将未被引用的字符串所占内存即使回收,以此节省内存。
字符串作为基础的数据结构,大量地应用在一些集合容器之中,尤其是一些散列集合,在散列集合中,存放元素都要根据对象的?hashCode()
?方法来确定元素的位置。由于字符串?hashcode
?属性不会变更,保证了唯一性,使得类似 HashMap,HashSet 等容器才能实现相应的缓存功能。由于 String 的不可变,避免重复计算?hashcode
,只有使用缓存的?hashcode
?即可,这样一来大大提高了在散列集合中使用 String 对象的性能。
在多线程中,只有不变的对象和值是线程安全的,可以在多个线程中共享数据。由于 String 天然的不可变,当一个线程”修改“了字符串的值,只会产生一个新的字符串对象,不会对其他线程的访问产生副作用,访问的都是同样的字符串数据,不需要任何同步操作。
由于字符串无论在任何 Java 系统中都广泛使用,会用来存储敏感信息,如账号,密码,网络路径,文件处理等场景里,保证字符串 String 类的安全性就尤为重要了,如果字符串是可变的,容易被篡改,那我们就无法保证使用字符串进行操作时,它是安全的,很有可能出现 SQL 注入,访问危险文件等操作。
通过本文,我们介绍 String 是不可变的,可以将它们的引用可以被当作一个普通的变量来使用,无论是在方法间,还是线程间传递它们,都不用担心它指向的实际 String 对象发生改变,并且不可变的特性也在语言层面和程序层面上带了许多好处,我们也应该在编程实践中多学习效仿,用 James Gosling,Java之父的话说就是,“我会尽可能地使用不可变对象”。
留言交流不过瘾?添加微信:zyc_enjoy
根据指引加入各种主题讨论群
每日一问
今日问题:
王师傅是卖鱼的,一斤鱼进价45元,现亏本大甩卖,顾客35元买了一公斤,给了王师傅100元假钱,王师傅没零钱,于是找邻居换了100元。事后邻居存钱过程中发现钱是假的,被银行没收了,王师傅又赔了邻居100元,请问王师傅一共亏了多少?
(留言说说你的答案和解析吧,关注公众号,发送口令:Q20190824,核对正确答案)
昨日问答:点击>>查看<<
推荐阅读
这几款好用超赞的 Google Chrome插件送给你!
IntelliJ IDEA 2019.2最新解读
Spring Cloud与Dubbo的完美融合之手
狡猾的 AI 工程师,编个故事骗走 2 亿人民币...
日均7亿交易量,如何设计高可用的MySQL架构?
签到计划
活动介绍:自律到极致-人生才精致:第13期
活动奖励:《Spring Cloud微服务:入门、实战与进阶》 x 10
扫描下放二维码,签到参与
点一点“阅读原文”小惊喜在等你
cs