Java中String类的经典问题、错误认知以及归纳总结

在学习过程中对String类的理解反复刷新,以此文记之,做归纳总结,也适合新手避坑。

以实用性考虑,环境为Java 8 以及 之后版本。

String类相比其它类特殊的地方在于有一个字符串常量池(StringTable),里面存着字面量的引用

(关于是实例还是引用的争论请看下方链接,个人站引用)

JVM 常量池中存储的是对象还是引用呢?
在这里插入图片描述
达成这个共识以后,我们从最简单的例子开始说起

对象创建

String str = "abc";

"abc"这个String对象是怎么创建的呢?

①在StringTable中查找,若有内容匹配的引用,则直接返回这个引用。若无内容匹配的String实例的引用,则在Java堆里创建一个对应内容的String对象,然后在StringTable记录下这个引用,并返回这个引用。

Java 中new String(“字面量”) 中 “字面量” 是何时进入字符串常量池的?

(感谢此回答,让我受益匪浅)

请留意这个过程,这个和后续提到的intern方法像亲兄弟一样。

入门问题一

String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2);
// 很简单 true
// 内存中只有一个 "hello" 对象被创建,同时被 s1 和 s2 共享。

现在将创建对象变得复杂一点

String str2 = new String(“abc”);

String str2 = new String(“abc”); 与 String str1 = “abc”; 有什么区别?

②因为此行代码构造新字符串要用到字面量"abc",所以要先完成"abc"这个对象的创建(见①),再完成另一个String对象的创建,str2指向后面这个实例。

注意1,我原先写的是“要先创建"abc"这个对象(见①),再new另一个String对象。”后来觉得不严谨,因为"abc"的开始创建比另一个晚,但是完成另一个早,所以换成了上述说法,若要深究,请移步。

深入理解 new String()

注意2,两个实例都在堆中。str2的value指向"abc"的value,两个对象的char[] value是一致的。(JDK9以后是byte[])

这就有了一个经典问题二

String str2 = new String(“hello”); 在内存中创建了几个对象?

由②可知,2个。

结合①和②,很容易解决问题三

String s1 = "javaEE";
String s2 = "javaEE";
String s3 = new String("javaEE");
String s4 = new String("javaEE");
System.out.println(s1 == s2);//true
System.out.println(s1 == s3);//false
System.out.println(s1 == s4);//false
System.out.println(s3 == s4);//false

"+"号拼接字符串

有变量参与的拼接和只有常量参与的拼接是不一样的。

只有常量参与的拼接有以下两种情况

全是字面量拼接

String s2 = "abc" + "123";

final修饰的常量拼接

final String s = "abc";
String s2 = s + "123";

上面均和String s2 = "abc123"是等价的,因此s2的创建过程参考①。

一旦有变量参与拼接,例如

String s = "abc";
String s2 = s1 + "123";

String s2 = new String("abc") + new String("123");

java会通过StringBuilder来进行字符串的拼接,通过append()方法,最后直接toString()返回。而这个toString()方法调用的其实是String(byte[] value, byte coder)这个构造器。注意这个构造器,传入的是字节数组而不是字符串"abc123",就不会创建"abc123"实例并且也不会在StringTable中记录,这意味着"abc123"的引用在StringTable中是找不到的! 接着,既然是调用的构造器,自然是在堆中new一个新对象。

结合上述说明很容易解决问题四和五

问题四

String s1 = "hello";
String s2 = "world";
String s3 = "helloworld";
String s4 = s1 + "world";//s4 字符串内容也 helloworld , s1 是变量, "wo
rld" 常量,变量 + 常量的结果在堆中
String s5 = s1 + s2;//s5 字符串内容也 helloworld , s1 和 s2 都是变量,
变量 + 变量的结果在堆中
String s6 = "hello" + "world";// 常量 + 常量 编译期间就可以确定结果
System.out.println(s3 == s4);//false
System.out.println(s3 == s5);//false
System.out.println(s3 == s6);//true

问题五

final String s1 = "hello";
final String s2 = "world";
String s3 = "helloworld";
String s4 = s1 + "world";//s4 字符串内容也 helloworld , s1 是常量, "wo
rld" 常量,常量 + 常量结果在常量池中
String s5 = s1 + s2;//s5 字符串内容也 helloworld , s1 和 s2 都是常量,
常量 + 常量 结果在常量池中
String s6 = "hello" + "world";// 常量 + 常量 结果在常量池中,因为编译
期间就可以确定结果
System.out.println(s3 == s4);//true
System.out.println(s3 == s5);//true
System.out.println(s3 == s6);//true

问题六

String str = "hello";
String str2 = "world";
String str3 ="helloworld";
String str4 = "hello".concat("world");
String str5 = "hello"+"world";
System.out.println(str3 == str4);//false
System.out.println(str3 == str5);//true

concat 方法也是调用String(byte[] value, byte coder)这个构造器,哪怕是两个常量对象拼接,结果也是在堆。

问题七

String s1 = new String("1") + new String("1");
String s2 = new String("1") + "1";
String s3 = new StringBuilder("1").append("1").toString();
String s4 = "1" + "1";
System.out.println(s1 == s4); //false
System.out.println(s2 == s4);//false
System.out.println(s3 == s4);//false

问题七你做对了,但可能还是有如下误区

误区一

过去我总以为,只要一个字符串对象被new出来,在StringTable中是存在它的字面量引用的,这是错误的。 比如:

String str = String s1 = new String("a") + new String("bc");

此时StringTable中会有"abc"吗?
不会的,StringTable中只有"a"和"bc"。

原因在加号拼接那一节讲过,不再赘述,这里再提,是想重点强调一下,这对后面很重要。

那么问题来了

什么情况下,字符串引用会被存入字符串常量池?

只有下面四种情况

1.字符串字面量:

String s1 = "Hello"; 

这个字符串 “Hello” 引用会被存入字符串常量池。

2.字符串连接(编译时常量):

String s2 = "Hello" + " World"; // 编译器在编译时将其优化为 "Hello World" 

这种情况下,“Hello World” 引用也会存入常量池。

3.常量表达式:

final String prefix = "Hello"; String s4 = prefix + " World"; 
// 由于 prefix 是 final,编译器可以优化并引用到常量池

4.使用 String.intern() 方法

String s3 = new String("Hello").intern(); 

如果常量池中已经存在 "Hello"引用,则返回常量池中的 "Hello"引用;如果不存在,则将当前"Hello"对象的引用添加到常量池中。

前三种之前说过,下面重点解释第四种

先看看注释

When the intern method is invoked, if the pool already contains a
string equal to this String object as determined by the equals(Object)
method, then the string from the pool is returned. Otherwise, this
String object is added to the pool and a reference to this String
object is returned.

若能在StringTable中找到字面量一样的对象引用,返回此引用。若不能找到,将调用对象的引用作为当前字面量的引用存入StringTable中,并返回此引用。

误区二

过去我总以为,调用intern方法时,如果在StringTable中不能找到字面量的引用,会新建一个字面量对象,并把当前对象的引用加入StringTable(眼熟不,这就是字面量String对象的创建过程啊),这是错误的。真实情况是,正如上面所说,不会创建新对象,而是将调用对象的引用作为当前字面量的引用存入StringTable中,并返回此引用。

也正是因为我带着误区二的观点,认为下面的结果是false,但实际上是true。在纠正过来以后,结果是true彻底说通了。

 String a = "a";String param = new String("param" + a);String intern = param.intern();String paramSame = "parama";System.out.println(param == paramSame);//true

由此可以轻松解决问题八

String s1 = "hello";
String s2 = "world";
String s3 = "helloworld";
String s4 = (s1 + "world").intern();// 在上一行已经将"helloworld"的引用放入StringTable中了
String s5 = (s1 + s2).intern();
System.out.println(s3 == s4);//true
System.out.println(s3 == s5);//true

问题九

String s1 = new String("1") + new String("1");
s1.intern();
String s2 = "11";//在上一行已经将"11"的引用s1放入StringTable中了
System.out.println(s1 == s2);//true

理解了上文所有的加粗文字后,可以解决知乎上的一众疑惑(对他们问题中的代码结果做出合理解释)

new一个String对象的时候,如果常量池没有相应的字面量真的会去它那里创建一个吗?我表示怀疑。

问题十

String a = "a";
String param = new String("param" + a);
String paramSame = param.intern();
System.out.println(param == paramSame);//true
//调用param.intern();的时候,在StringTable中将param作为"parama"的引用,并且返回param,这两者当然是相等的。

问题十一

String a = "a";
String param = "b" + a;
System.out.println(param.intern() == "ba"); //true
//先调用param.intern(),将param作为"ba"的引用存入StringTable,
//再遇到"ba"时,StringTable中已有"ba"的引用,不再创建新对象,直接返回,此时"ba"的引用是param
System.out.println(param == "ba");//true

问题十二

String a = "a";
String param = "b" + a;
System.out.println("ba" == param.intern()); //true
//StringTable中没有"ba"的引用,会先创建对象,并返回引用。再执行param.intern()时,发现已经有"ba"的引用,直接返回。
//因此相等
System.out.println(param == "ba");//false
//这是两个不同对象的引用

Java 中new String(“字面量”) 中 “字面量” 是何时进入字符串常量池的?

问题十三

 String s1=new String("he")+new String("llo"); s1.intern();   String s2="hello";  System.out.println(s1==s2);//true

问题十四

 String s1=new String("he")+new String("llo");String s2=new String("h")+new String("ello");String s3=s1.intern();String s4=s2.intern();System.out.println(s1==s3);//trueSystem.out.println(s1==s4);//true

最后两个问题,我就不解释了,按照上文的规则,都能说得通,可以好好想想,累了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/52129.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Hackademic.RTB1靶场实战【超详细】

靶机下载链接:https://download.vulnhub.com/hackademic/Hackademic.RTB1.zip 一、主机探测和端口扫描 nmap 192.168.121.0/24 ip:192.168.121.196 端口:22、80 二、访问80端口 发现target可点击 点击后跳转,页面提示目标是读取到 key.txt 文件 fin…

Enhancing Octree-Based Context Models for Point Cloud Geometry Compression 论文笔记

1. 论文基本信息 发布于: IEEE SPL 2024 2. 创新点 分析了基于 one-hot 编码的交叉熵损失函数为什么不能准确衡量标签与预测概率分布之间的差异。介绍了 ACNP 模块,该模块通过预测占用的子节点数量来增强上下文模型的表现。实验证明了ACNP模块在基于八…

【Java】 力扣 最大子数组和

目录 题目链接题目描述思路代码 题目链接 53.最大子数组和 题目描述 思路 动态规划解析: 状态定义: 设动态规划列表 dp ,dp[i] 代表以元素 nums[i] 为结尾的连续子数组最大和。 为何定义最大和 dp[i] 中必须包含元素 nums[i] :…

前端css动画缩放transform: scale()

transform: scale(2) scale等比例放大 大于1是放大 小于1是缩小 负值是倒着放大 scaleX scaleY 可以单独设置只在x轴y轴放大 改变中心点放大的位置 左上 left top 左下 left bottom 左中 left center 右上 …

Python之列表的基本使用

列表 一、什么是列表二、创建 Python 列表三、二维列表四、索引和切片五、运算符六、列表的函数(1)len(列表名)(2)min(列表名)(3)max(列表名)(4)s…

探索顶级PDF水印API:PDFBlocks(2024年更新)

引言 在一个敏感信息常常面临风险的时代,能够轻松高效地保护文档的能力至关重要。PDF水印已成为企业和个人寻求保护其知识产权、确保文件保密性的基本工具。 PDFBlocks 文字水印 API是什么? PDFBlocks API 提供了一个强大的解决方案,用于在…

如何使用博达网站群管理平台的树状导航

1 介绍 由于网站建设需要,需在首页的左边竖栏部分使用树状导航。我又过了一遍《网站群管理平台用户手册》,没发现如何在网站的首页设置树状导航组件。昨天,我之所以在创建树状导航上不知所措,是因为平台本身有一些误导&#xff0…

如何用Java SpringBoot+Vue搭建校内跑腿业务系统?实战教程解析

✍✍计算机毕业编程指导师 ⭐⭐个人介绍:自己非常喜欢研究技术问题!专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目:有源码或者技术上的问题欢迎在评论区一起讨论交流! ⚡⚡ Java、…

ASM实例的SPILE 存储在ASM的磁盘组上时,集群要如何去获取SPFILE并启动ASM实例?(1)

从11g R2 开始,ASM spfile 会自动存储在安装集群软件时创建的第一个磁盘组中,一般为OCR磁盘组。由于投票盘/OCR 存储在 ASM 上,因此需要在节点上启动 ASM。要启动 ASM,需要其 SPFILE 。但 SPFILE 仅位于 ASM 磁盘组上。集群是如何…

【html+css 绚丽Loading】 - 000009 五行逆流珠

前言:哈喽,大家好,今天给大家分享htmlcss 绚丽Loading!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏关注哦 &#x1f495…

[000-01-018].第3节:Linux环境下ElasticSearch环境搭建

我的后端学习笔记大纲 我的ElasticSearch学习大纲 1.Linux系统搭建ES环境: 1.1.单机版: a.安装ES-7.8版本 1.下载ES: 2.上传与解压:将下载的tar包上传到服务器software目录下,然后解压缩:tar -zxvf elasticsearch-7…

Excel求和方法之

一 SUM(),选择要相加的数,回车即可 二 上面的方法还不够快。用下面这个 就成功了 三 还有一种一样快的 选中之后,按下Alt键和键(即Alt)

深度学习------------------卷积神经网络(LeNet)

目录 LeNet网络手写的数字识别MNIST总结卷积神经网络(LeNet) 问题 LeNet网络 手写的数字识别 MNIST ①输入的是:3232的image ②放到一个55的卷积层里面(为什么是5?因为32-x128,∴x5)&#xff0c…

计算机毕业设计PySpark+Flask bilibili弹幕情感分析 B站视频数据可视化 B站爬虫 机器学习 深度学习 NLP自然语言处理 大数据毕业设计

### 开题报告:基于PySpark和Flask的B站弹幕情感分析系统 #### 一、研究背景 在网络视频平台的用户互动中,弹幕(Danmaku)作为一种实时评论的形式,已经成为观众表达观点和情感的重要方式。尤其是在B站(哔哩…

Go Roadmap-Basics中文笔记

Go Roadmap-Basics 地址:https://roadmap.sh/golang 简介:Github star No.6 学习路线 Go 中译版 Learn the Basics Go特点:静态类型,运行速度快,编译语言,编译速度快,自动垃圾回收&#xff…

【GH】【EXCEL】P4: Chart

文章目录 data and chartdonut chart (radial chart)Radial Chart bar chartBar Chart line chartLine Chart Scatter ChartScatter Chart Surface ChartSurface Chart Chart DecoratorsChart Decorators Chart GraphicsChart Graphics data and chart donut chart (radial cha…

C语言04--数组超详解

1.基本概念 逻辑:一次性定义多个相同类型的变量,并存储到一片连续的内存中语法: 数据类型 数组名字 [ 数据的量 ] ; 示例: int a[5]; int Num ; 语法释义: a 是数组名,即这片连续内存的名称[5] …

基于WEB的旅游推荐系统设计与实现

TOC springboot280基于WEB的旅游推荐系统设计与实现 第1章 绪论 1.1选题动因 当前的网络技术,软件技术等都具备成熟的理论基础,市场上也出现各种技术开发的软件,这些软件都被用于各个领域,包括生活和工作的领域。随着电脑和笔…

五、2 移位操作符赋值操作符

1、移位操作符 2、赋值操作符 “ ”赋值,“ ”判断是否相等 1)连续赋值 2)复合赋值符

VS Code开发C#(.NET)之快速入门

本篇快速介绍在VS Code中开发C#的完整说明和示例: 环境准备 安装VS Code: 前往Visual Studio Code官网 下载并安装VS Code。 安装.NET SDK: C#是基于.NET框架的,因此需要安装 .NET SDK。 前往 .NET官网 下载并安装适用于操…