0
点赞
收藏
分享

微信扫一扫

在 Java 中,什么条件下两个 String 会是同一个对象?


这段时间工作上经常用遇到 ​​String​​ 对象比较的问题,这是一个比较基础的问题,但有时候对其原因还是有些迷惑,所以稍微总结一下。

一、核心要点

今天我们来讲讲 ​​Java​​​ 语言中的 ​​String​​​,在一定条件下两个 ​​String​​​ 会是同一个对象,这是怎么回事呢?​​Java​​​ 语言中的 ​​String​​​ 相信大家都很熟悉,但是 ​​Java​​​ 语言中的两个 ​​String​​ 什么条件下会是同一个对象是怎么回事呢,下面就让小编带大家一起了解吧。

​Java​​​ 语言中的两个 ​​String​​​ 什么条件下会是同一个对象,其实就是两个 ​​String​​​ 相等的情况下会是同一个对象,大家可能会很惊讶 ​​Java​​​ 语言中的两个 ​​String​​ 怎么会是同一个对象呢?但事实就是这样,小编也感到非常惊讶。

这就是关于 ​​Java​​​ 语言中的 ​​String​​​ 在什么条件下两个 ​​String​​ 会是同一个对象的事情了,大家有什么想法呢,欢迎在评论区告诉小编一起讨论哦!

二、基础知识

2.1 String 是什么?

​String​​​ 不同于其他 8 大基础数据类型,它是一个对象。它还是一个比较特殊的对象,因为他还可以通过 ​​=​​ 直接赋值。

2.2 常量池介绍

在 JAVA 中常量池分三种。

对于 Integer 和 Long 等基础数据类型的包装类也有类似的常量缓存,本文就不详细说明了。

Class 常量池:

Class 常量池存放在 ​​*.class​​​ 文件中,在编译阶段生成,也被称为静态常量池,用于存储字面量和符号引用。字面量主要包含文本字符串、被声明为 ​​final​​ 的常量值和基本数据类型的值,符号引用则包括类和接口的全限定名、字段的名称和描述符、方法的名称和描述符。

运行时常量池:

运行时常量池包也被称为动态常量池,但 ​​class​​​ 文件被加载到内存后,虚拟机会将 ​​class​​ 文件常量池里的内容转移到运行时常量池。运行时常量池具有动态性,运行期间也可能将新的常量放入池中。

字符串常量池:

字符串常量池又称为字符串池,全局字符串池,本质上是个 ​​HashSet<String>​​,是一个纯运行时的常量池。字符串常量池是 JVM 为了提升性能,避免字符串重复创建而维护的一块特殊的内存空间,用于存储堆中字符串对象的引用。

2.3 两种不同的创建方式

字面量赋值:

String s = "nineya";

这种创建方式 ​​JVM​​​ 将搜索字符串常量池中是否存在需要创建的 ​​nineya​​ 字符串的地址引用,如果不存在则会在堆中开辟空间存储字符串对象,常量池中开辟空间存储该字符串对象的地址引用,如果存在则直接使用常量池中引用的地址。在栈中也将开辟空间,用于存储字符串的地址值。

用new创建:

String s = new String("nineya");

这种创建方式 ​​JVM​​​ 也将搜索字符串常量池中是否存在需要创建的 ​​nineya​​ 字符串的地址引用,如果不存在则会在堆中开辟空间存储字符串对象,常量池中开辟空间存储该字符串对象的地址引用,如果存在则直接使用常量池中引用的地址。

除此之外,在堆和栈中也将会开辟空间,堆空间中将创建一个字符串对象,引用常量池中引用的字符串对象的字符内容,栈空间则存储堆中字符串对象的地址。

三、字符串比较

比较一:

String s1 = "nineya";
String s2 = "nineya";
System.out.println(s1 == s2);

// 输出结果为:true

如上代码 ​​JVM​​​ 将会把 2 个 ​​nineya​​ 字符串都作为字面量放入 Class 常量池,然后在类加载阶段创建字符串实例,并在字符串常量池中存储其引用,所以它们会是同一个对象。

比较二:

String s1 = new String("nineya");
String s2 = new String("nineya");
System.out.println(s1 == s2);

// 输出结果为:false

如上 ​​s1​​​ 和 ​​s2​​​ 都使用 ​​new​​ 进行创建,将在堆中新创建一个字符串对象,所以不会是同一个对象。

比较三:

final String s1 = "nineya";
String s2 = s1 + ".com";
String s3 = "nineya.com";
System.out.println(s2 == s3);

// 输出结果为:true

由于 ​​s1​​​ 有 ​​final​​​ 关键字修饰,所以 ​​s1​​​ 与 ​​.com​​​ 的拼接在编译时便会直接完成,都将被放入 Class 常量池,所以 ​​s2​​​ 和 ​​s3​​ 是同一个对象。

比较四:

String s1 = "nineya";
String s2 = s1 + ".com";
String s3 = "nineya.com";
System.out.println(s2 == s3);

// 输出结果为:false

由于 ​​s1​​​ 是一个变量,所以 ​​s1​​​ 和 ​​.com​​​ 的拼接将使用 ​​StringBuilder​​​ 的 ​​append​​​ 方法生成新对象,不会搜索字符串常量池,而 ​​s3​​​ 依旧是常量池引用的对象,所以 ​​s2​​​ 和 ​​s3​​ 不是同一个对象。

比较五:

String s1 = "nineya";
String s2 = s1 + ".com";
String s3 = "nineya.com";
System.out.println(s2.intern() == s2);
System.out.println(s2.intern() == s3);
System.out.println(s3 == s3.intern());

// 输出结果为:false/true/true

​intern()​​ 方法的作用是判断字符串常量池是否存在当前字符串的常量,如果存在则取出该常量,如果不存在则将当前对象放入常量。

由于 ​​s3​​​ 在类加载时已经被放入字符串常量池了, 所以 ​​s2.intern()​​​ 取出的字符串对象是 ​​s3​​​,所以 ​​s2.intern() == s2​​​ 不是同一个对象为 ​​false​​​,​​s2.intern() == s3​​​ 是同一个对象为 ​​true​​。

而 ​​s3​​​ 就是字符串常量对象,所以 ​​s3.intern()​​​ 取出的对象为其本身,所以 ​​s3 == s3.intern()​​​ 为 ​​true​​。


举报

相关推荐

0 条评论