求求你,别问了,Java字符串是不可变的
最近,又有好几个小伙伴问我这个问题:“二哥,为什么 Java 的 String 要设计成不可变的啊?”说实话,这也是一道非常经典的面试题,面试官超喜欢问。我之前写过这方面的文章,现在读起来似乎不太满意,所以我决定再啰嗦最后一次,交出一份更满意的答卷,让小伙伴们在面试官面前更从容一些,更有底气一些。

关于不可变对象,还有这样一个小故事。Java 之父詹姆斯高司令曾在一次采访中被问及这样一个问题:“高司令,应该什么时候使用不可变对象啊?”你猜高司令怎么回答?
如有可能,我愿意任何时候都使用不可变对象。
这就是高司令的答案,那有的小伙伴可能不服,老人家会说中文,你瞎扯吧你。也对哈,那就上英文呗:
I would use an immutable whenever I can.
这下彻底被打服了吧?老人家还说,不可变有着非常强大的功能,比如说,缓存、安全性、高性能等等。
01、什么是不可变对象
不可变对象在创建后,它的内部状态会保持不变,这就意味着,一旦我们将一个对象分配给一个变量,就无法再通过任何方式更改对象的状态了。
关于不可变对象的更多信息,可以查看我之前写的另外一篇文章——这次要说不明白immutable类,我就怎么地,看完啥都明白了。你看,写系列文章的好处就是这样,不需要重复造轮子,用到的时候直接搬出来套上就行了。
02、为什么 String 是不可变的
重点来了啊,为什么 String 是不可变的?原因可以从四个方面说起,缓存、安全性、同步和高性能。
1)字符串常量池
字符串恐怕是 Java 中最常用的数据形式了,如果字符串非要谦虚地说自己是老二,就没有人敢说自己是老大。
因此,把字符串缓存起来,并且重复使用它们会节省大量堆空间(堆内存用来存储 Java 中的对象,无论是成员变量、局部变量,还是类变量,它们指向的对象都存储在堆内存中),因为不同的字符串变量引用的是字符串常量池中的同一个对象。这也正是字符串常量池存在的目的。
字符串常量池是 Java 虚拟机用来存储字符串的一个特殊的区域,由于字符串是不可变的,因此 Java 虚拟机可以在字符串常量池中只为同一个字符串存储一个字符串副本来节省空间。
字符串常量池的主要使用方法有两种:
- 直接使用双引号声明出来的字符串对象会直接存储在常量池中。
- 否则,可以使用 String 类提供的
intern()方法强制将当前字符串放入常量池中——常量池中查询不到当前字符串。
来看下面这段代码:
String s1 = "沉默王二";
String s2 = "沉默王二";
System.out.println(s1 == s2); // true
由于字符串常量池的存在,所以两个不同的变量都指向了池中同一个字符串对象,从而节省了稀缺的内存资源。如果是通过 new 关键字创建的对象,则需要新的堆空间。

放心,关于字符串常量池,后面有时间的话,我再单独写一篇文章详细地说一说。
2)安全性
字符串在 Java 应用程序中的使用范围非常广,几乎无处不在,比如说存储用户名、密码、数据库连接地址等等这些非常敏感的信息,因此,必须要保证 String 类的绝对安全性。
来考虑一下下面这段代码:
void criticalMethod(String userName) {
// 检查用户名是否合法
if (!isAlphaNumeric(userName)) {
throw new SecurityException();
}
// 初始化数据库连接
initializeDatabase();
// 准备修改用户状态
connection.executeUpdate("UPDATE members SET status = 'active' " +
" WHERE username = '" + userName + "'");
}
通常情况下,用户名由客户端传递到服务器端,服务器端接收后要先对用户名进行检查,再进行其他操作,因为客户端传递过来的信息不一定值得信任。
如果字符串是可变的,那么我们在执行 executeUpdate 更新数据库的时候,就有点不放心,因为即便是安全性检查通过了,字符串仍然有可能被修改。
在调用 isAlphaNumeric() 方法进行安全性检查期间,userName 的值仍然有可能被 criticalMethod() 方法的调用者进行篡改,就容易造成 SQL 注入。
但如果字符串是不可变的,这方面的担忧就不存在了。因为在执行更新之前,字符串的值是确定的,就是我们检查安全性之后的值。
3)线程安全
由于字符串是不可变的,因此可以在多线程之间共享,如果一个线程把字符串的值修改为另外一个,那么就会在字符串常量池中创建另外一个字符串,原有的字符串仍然会保持不变。
不过,很遗憾,我还不知道怎么从代码层面上去证明这一点,只能纯理论 yy 一下。小伙伴谁有办法的,教教我,在线等的那种。
4)哈希码
字符串广泛应用于 HashMap、HashTable、HashSet 等需要哈希码作为键的数据结构中,在对这些哈希表进行操作的时候,需要频繁调用 hashCode() 方法来获取键的哈希码。
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
由于字符串是不可变性,这就保证了键值的哈希值不会发生改变,因此在第一次调用 String 类的 hashCode() 方法时,就对哈希值进行了缓存,此后,就一直返回相同的值。
/** Cache the hash code for the string */
private int hash; // Default to 0
public int hashCode() {
int h = hash;
if (h == 0 && !hashIsZero) {
h = isLatin1() ? StringLatin1.hashCode(value)
: StringUTF16.hashCode(value);
if (h == 0) {
hashIsZero = true;
} else {
hash = h;
}
}
return h;
}
由于哈希值被缓存了,这在另外一种层面上提高了哈希表的访问性能,因为哈希值不用重新计算了。
假如字符串是可变的,那就意味着哈希码会有多个,在通过键获取值的时候,就不一定能够获取到对的值了。
你看,字符串常量池的存在,哈希码的存在,在很大程度上提高了程序的性能。
03、总结
好了,我亲爱的小伙伴们,以上就是本文的全部内容了。我相信你一定对字符串的不可变性有了充足的了解,由于字符串是不可变的,因此我们可以将它看作是一个特殊的基本数据类型,哪怕是在多线程的环境下,也不用担心它的值是否会发生改变。
如果觉得文章对你有点帮助,请微信搜索「 沉默王二 」第一时间阅读。
本文已收录 GitHub,传送门~ ,里面更有大厂面试完整考点,欢迎 Star。
我是沉默王二,一枚有颜值却靠才华苟且的程序员。关注即可提升学习效率,别忘了三连啊,点赞、收藏、留言,我不挑,嘻嘻。
求求你,别问了,Java字符串是不可变的的更多相关文章
- 灵魂拷问:为什么 Java 字符串是不可变的?
在逛 programcreek 的时候,发现了一些精妙绝伦的主题.比如说:为什么 Java 字符串是不可变的?像这类灵魂拷问的主题,非常值得深思. 对于绝大多数的初级程序员来说,往往停留在" ...
- 为什么Java字符串是不可变对象?
转自 http://developer.51cto.com/art/201503/468905.htm 本文主要来介绍一下Java中的不可变对象,以及Java中String类的不可变性,那么为什么Ja ...
- 聊一聊Java字符串的不可变
前言 在 Java 开发中 String (字符串)对象是我们使用最频繁的对象,也是很重要的对象.正是使用得如此频繁,String 在实现层面上不断进行优化,从 Java6 到 Java7,再到 Ja ...
- 为什么Java中的字符串是不可变的?
原文链接:https://www.programcreek.com/2013/04/why-string-is-immutable-in-java/ java字符串是不可变的.不可变类只是一个不能修改 ...
- [译]关于Java 字符串最常被问到的十个问题
(说明,该文章翻译自Top 10 questions of Java Strings) 下面是关于Java字符串最常被问到的十个问题 1.怎么去比较字符串?使用==还是使用equals()? 简单来说 ...
- 【JAVA零基础入门系列】Day6 Java字符串
字符串,是我们最常用的类型,每个用双引号来表示的串都是一个字符串.Java中的字符串是一个预定义的类,跟C++ 一样叫String,而不是Char数组.至于什么叫做类,暂时不做过多介绍,在之后的篇章中 ...
- Java字符串常量池是什么?为什么要有这种常量池?
简单介绍 Java中的字符串常量池(String Pool)是存储在Java堆内存中的字符串池.我们知道String是java中比较特殊的类,我们可以使用new运算符创建String对象,也可以用双引 ...
- 看完肯定懂的 Java 字符串常量池指南
字符串问题可谓是 Java 中经久不衰的问题,尤其是字符串常量池经常作为面试题出现.可即便是看似简单而又经常被提起的问题,还是有好多同学一知半解,看上去懂了,仔细分析起来却又发现不太明白. 背景说明 ...
- OMG,12 个精致的 Java 字符串操作小技巧,学它
字符串可以说是 Java 中最具有代表性的类了,似乎没有之一哈,这就好像直播界的李佳琪,脱口秀中的李诞,一等一的大哥地位.不得不承认,最近吐槽大会刷多了,脑子里全是那些段子,写文章都有点不由自主,真的 ...
随机推荐
- 基于postman测试接口(整套接口测试)
基于postman测试接口(整套接口测试) 可以解决的问题 几百个接口人工测试接口过于繁杂 大多测试无法使用请求结果当参数 可以使用随机参数 支持swagger信息导入 随账号持久化保存数据 对集合一 ...
- 【Mac】pip自定义源【永久有效】
鉴于国内网络环境,pip安装比较慢已成为不争的事实,通过以下几步轻松解决 1.创建文件夹 mkdir -/.pip 2.创建配置文件 vim -/.pip/pip.conf mkdir ~/.p ...
- springboot中yml常用配置
server: port: 8080 spring: datasource: #数据源配置 driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc: ...
- SpringCloud(四)- Hystris简介及@EnableCircuitBreaker 和 @HystrixCommand 注解的使用
唯能极于情,故能极于剑有问题或错误请及时联系小编或关注小编公众号 “CodeCow”,小编一定及时回复和改正,期待和大家一起学习交流 此文由四部分组成(Hystris简介.@EnableCircuit ...
- Magicodes.SwaggerUI 已支持.NET Core 3.1
Magicodes.SwaggerUI 通过配置文件简单配置即可快速完成SwaggerUI的配置,包括: SwaggerUI的文档信息 API分组 API隐藏 API JSON生成(枚举.API架构I ...
- HttpSession之表单的重复提交 & 验证码
如果采用 HttpServletResponse.sendRedirct() 方法将客户端重定向到成功页面,将不会出现重复提交问题 1.表单的重复提交 1). 重复提交的情况: ①. 在表单提交到一个 ...
- nodejs安装及环境变量的配置
首先,nodejs安装到C盘的 建议安装到其他盘里,这是为了防止电脑出故障后C盘下载的东西会缺失 第一次安装nodejs的,第一步打开“nodejs官网:https://nodejs.org/zh-c ...
- 公有继承中派生类Student对基类Person成员的访问 代码参考
#include <iostream> #include <cstring> using namespace std; class Person { private: char ...
- R语言入门一
一.数据分析相关概念 数据:是指对事物或对象各方面进行描述的符号,包括事物的基本属性.特征.性质.状态.相互关系等:比如描述人的数据有:身高.年龄.性别.兴趣.性格.婚姻状态等等. 分析:是指把事物或 ...
- Physic Design:Floorplan算法概览
仅用于学习交流,转载请联系本人. 1 floorplan是什么 floorplan常被翻译成布图规划,是指在芯片级别上对模块进行布局,也就是哪个单元放在什么地方,但是单元内部的具体布局并不关心.该步骤 ...