String 的不可变真的是由于 final 吗?

🎓 尽人事,听天命。博主东南大学硕士在读,热爱健身和篮球,乐于分享技术相关的所见所得,关注公众号 @ 飞天小牛肉,第一时间获取文章更新,成长的路上咱们一块儿进步java

🎁 本文已收录于 「CS-Wiki」Gitee 官方推荐项目,现已累计 1.6k+ star,致力打造完善的后端知识体系,在技术的路上少走弯路,欢迎各位小伙伴前来交流学习git

🍉 若是各位小伙伴春招秋招没有拿得出手的项目的话,能够参考我写的一个项目「开源社区系统 Echo」Gitee 官方推荐项目,目前已累计 600+ star,基于 SpringBoot + MyBatis + MySQL + Redis + Kafka + Elasticsearch + Spring Security + ... 并提供详细的开发文档和配套教程。公众号后台回复 Echo 能够获取配套教程,目前尚在更新中面试


String 为啥不可变?由于 String 中的 char 数组被 final 修饰。这套回答相信各位已经背烂了,But 这并不正确!算法

  • 面试官:讲讲 StringStringBuilderStringBuffer 的区别
  • 我:String 不可变,而 StringBuilderStringBuffer 可变,叭叭叭 ......
  • 面试官:String 为何不可变?
  • 我:Stringfinal 修饰,这说明 String 不可继承;而且String 中真正存储字符的地方是 char 数组,这个数组被 final 修饰,因此 String 不可变
  • 面试官:String 的不可变真的是由于 final 吗?
  • 我:是.....是的吧
  • 面试官:OK,你这边还有什么问题吗?
  • 我:卒......

什么是不可变?

《Effective Java》中对于不可变对象(Immutable Object)的定义是:对象一旦被建立后,对象全部的状态及属性在其生命周期内不会发生任何变化。这就意味着,一旦咱们将一个对象分配给一个变量,就没法再经过任何方式更改对象的状态了。数据库

String 不可变的表现就是当咱们试图对一个已有的对象 "abcd" 赋值为 "abcde",String 会新建立一个对象:后端

String 为何不可变?

String 用 final 修饰 char 数组,这个数组没法被修改,这么说确实没啥问题。数组

可是!!!这个没法被修改仅仅是指引用地址不可被修改(也就是说栈里面的这个叫 value 的引用地址不可变,编译器不容许咱们把 value 指向堆中的另外一个地址),并不表明存储在堆中的这个数组自己的内容不可变。举个例子:缓存

若是咱们直接修改数组中的元素,是彻底 OK 的:安全

那既然咱们说 String 是不可变的,那显然仅仅靠 final 是远远不够的网络

1)首先,char 数组是 private 的,而且 String 类没有对外提供修改这个数组的方法,因此它初始化以后外界没有有效的手段去改变它;

2)其次,String 类被 final 修饰的,也就是不可继承,避免被他人继承后破坏;

3)最重要的!是由于 Java 做者在 String 的全部方法里面,都很当心地避免去修改了 char 数组中的数据,涉及到对 char 数组中数据进行修改的操做所有都会从新建立一个 String 对象。你能够随便翻个源码看看来验证这个说法,好比 substring 方法:

为何要设计成不可变的呢?

1)首先,字符串常量池的须要

咱们来回顾一下字符串常量池的定义:大量频繁的建立字符串,将会极大程度的影响程序的性能。为此,JVM 为了提升性能和减小内存开销,在实例化字符串常量的时候进行了一些优化:

  • 为字符串开辟了一个字符串常量池 String Pool,能够理解为缓存区
  • 建立字符串常量时,首先检查字符串常量池中是否存在该字符串
  • 若字符串常量池中存在该字符串,则直接返回该引用实例,无需从新实例化;若不存在,则实例化该字符串并放入池中。

以下面的代码所示,堆内存中只会建立一个 String 对象:

String str1 = "hello";
String str2 = "hello";

System.out.println(str1 == str2) // true

假设 String 容许被改变,那若是咱们修改了 str2 的内容为 good,那么 str1 也会被修改,显然这不是咱们想要看见的结果。

2)另一点也比较容易想到,String 被设计成不可变就是为了安全

做为最基础最经常使用的数据类型,String 被许多 Java 类库用来做为参数,若是 String 不是固定不变的,将会引发各类安全隐患。

举个例子,咱们来看看将可变的字符串 StringBuilder 存入 HashSet 的场景:

咱们把可变字符串 s3 指向了 s1 的地址,而后改变 s3 的值,因为 StringBuilder 没有像 String 那样设计成不可变的,因此 s3 就会直接在 s1 的地址上进行修改,致使 s1 的值也发生了改变。因而,糟糕的事情发生了,HashSet 中出现了两个相等的元素,破坏了 HashSet 的不包含重复元素的原则。

另外,在多线程环境下,众所周知,多个线程同时想要修改同一个资源,是存在危险的,而 String 做为不可变对象,不能被修改,而且多个线程同时读同一个资源,是彻底没有问题的,因此 String 是线程安全的。

String 真的不可变吗?

想要改变 String 无非就是改变 char 数组 value 的内容,而 value 是私有属性,那么在 Java 中有没有某种手段能够访问类的私有属性呢?

没错,就是反射,使用反射能够直接修改 char 数组中的内容,固然,通常来讲咱们不这么作。

看下面代码:

总结

总结来讲,并非由于 char 数组是 final 才致使 String 的不可变,而是为了把 String 设计成不可变才把 char 数组设置为 final。下面是一些建立不可变对象的简单策略,固然,也并不是全部不可变类都彻底遵照这些规则:

  • 不要提供 setter 方法(包括修改字段的方法和修改字段引用对象的方法);
  • 将类的全部字段定义为 final、private 的;
  • 不容许子类重写方法。简单的办法是将类声明为 final,更好的方法是将构造函数声明为私有的,经过工厂方法建立对象;
  • 若是类的字段是对可变对象的引用,不容许修改被引用对象。

🎉 关注公众号 | 飞天小牛肉,即时获取更新

  • 博主东南大学硕士在读,利用课余时间运营一个公众号『 飞天小牛肉 』,2020/12/29 日开通,专一分享计算机基础(数据结构 + 算法 + 计算机网络 + 数据库 + 操做系统 + Linux)、Java 基础和面试指南的相关原创技术好文。本公众号的目的就是让你们能够快速掌握重点知识,有的放矢。但愿你们多多支持哦,和小牛肉一块儿成长 😃
  • 并推荐我的维护的开源教程类项目: CS-Wiki(Gitee 推荐项目,现已累计 1.6k+ star), 致力打造完善的后端知识体系,在技术的路上少走弯路,欢迎各位小伙伴前来交流学习 ~ 😊
  • 若是各位小伙伴春招秋招没有拿得出手的项目的话,能够参考我写的一个项目「开源社区系统 Echo」Gitee 官方推荐项目,目前已累计 600+ star,基于 SpringBoot + MyBatis + MySQL + Redis + Kafka + Elasticsearch + Spring Security + ... 并提供详细的开发文档和配套教程。公众号后台回复 Echo 能够获取配套教程,目前尚在更新中。
相关文章
相关标签/搜索