015:为什么Java中的字符串对象是不可变的
所谓不可变对象,是指一个对象在创建后,它的内部状态不会被改变的对象。这意味着当我们将一个不可变对象的引用赋值给某个变量后,我们就不能改变该对象的内部状态。 James Gosling也说过——Java开发者应该尽量使用不可变对象。
在Java中将String对象设置为不可变对象的好处很多,例如:缓存、安全、同步、性能等方面。
字符串对象不可变的好处
字符串共享
字符串常量池:字符串常量池是JVM中的一块特殊区域(1.7之前存放在perm区,1.8之后存放在堆上),用来存放字符串对象的值。在JVM中字符串是不可变的,因此JVM对于相同的字符序列,可以只保存一份,这个特性称之为“interning”。由于字符串是JVM中最常见的对象,因此实现字符串共享可以节省很多堆内存。
Why_String_Is_Immutable_In_Java.jpg
有两种方式定义的字符串,可以存放在常量池中:
- 使用常量字符串初始化字符串变量
String s1 = "Hello World";
String s2 = "Hello World";
System.out.printlin(s1 == s2); //结果为true
- 调用String对象的intern方法,需要注意的是:直接通过String的构造方法初始化的字符串对象,它的值并没有存放在字符串常量池,需要对该对象调用intern方法之后,才会将它的值放入字符串常量池。
String s1 = "Hello World";
String s2 = new String("Hello World");
System.out.println(s1 == s2); //结果为false
s2 = s2.intern();
System.out.println(s1 == s2); //结果为true
安全性
Java应用中使用字符串对象存放一些敏感信息:用户名、密码、连接地址、IP地址等等。Java中类加载器加载类的时候,也是根据类的名字去文件系统中的对应路径去查找的,类的名称、对应的路径,都是使用字符串对象存储的。
将字符串对象设计为不可变的,就意味着这个敏感信息一经生成就不会被改变(有点现在流行的区块链的思路)。
常见的安全检查流程有两个步骤:(1)校验安全信息;(2)进行敏感操作。如果字符串对象是可变的,则在做完第(1)步安全校验后这个字符串对象依然可能被改变。例如,我们现在在维护一个用户服务,提供了更改用户昵称的服务,业务逻辑是先检查用户昵称的合法性,然后再进行数据库的操作,如果字符串对象是可变的,那么第一步的合法性检查就没有意义了。
并发同步
不可变对象天然具备线程安全性,因为不用担心两个线程同时修改该对象时候产生的争用问题。假设字符串变量str = "hello"
被多个线程同时使用,如果在某个线程中对str赋了新的字符串值,那么就会在字符串常量池中生成一份新的字符串,不会有并发争用。
Hashcode缓存
在Java集合框架的很多数据结构中都用到了字符串对象,例如HashMap、HashTable、HashSet等等,在这些数据结构的实现过程中,都使用hashcode()方法来进行hash操作。
由于字符串对象的不变性,JDK将它的hashcode()做了缓存,这样对于同一个字符串对象,只会在第一次调用它的hashcode()方法的时候进行计算,后面的调用直接使用缓存中的值,这缓存也提升了集合数据结构的性能。
结论
这个问题考察得比较细致,需要用对应的案例进行理解,不可变的字符串对象对于Java应用来说不可或缺,下图是写这篇文章时候用到的思维导图:
字符串对象的不可变 (1).png
- Python之内置函数
- Python中的logger和handler到底是个什么鬼
- 1570. [POJ3461]乌力波
- biztalk rosettanet 自定义 pip code
- Python之线程
- 3555: [Ctsc2014]企鹅QQ
- 【实战】RFID Hacking(1):看我如何突破门禁潜入FreeBuf大本营
- P2885 [USACO07NOV]电话线Telephone Wire
- 实战-Fluxion与wifi热点伪造、钓鱼、中间人攻击、wifi破解
- 【下载】PyTorch实现的神经网络翻译框架——机器翻译工具包 nmtpytorch
- P2605 [ZJOI2010]基站选址
- MYSQL之索引原理与慢查询优化
- MYSQL之视图、触发器、存储过程、函数、事物、数据库锁和数据库备份
- P1452 Beauty Contes
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- JeecgBoot环境搭建及前后端项目启动
- Windows下MySQL 8.x和8.x以前版本的安装步骤
- Confluence 6 移动应用 app 管理员需要考虑什么
- leetcode链表之反转链表
- Prometheus监控神器-Kubernetes篇(一)
- Elastic:Elasticsearch 的分片管理策略
- 【Vulnhub】SkyDogCTF
- 「搜索引擎」TF-IDF 文档相关度评分
- 36.Python的字符串方法format
- Cypress系列(43)- wait() 命令详解
- Docker常用方法总结
- graph attention network(ICLR2018)官方代码详解(tensorflow)-稀疏矩阵版
- graph attention network(ICLR2018)官方代码详解(tensorflow)
- 在SAP Spartacus产品明细页面用outlet显示自定义数据
- 如何在SAP Spartacus自定义Component里消费数据