这段时间工作上经常用遇到 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

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

比较三:

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

// 输出结果为:true

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

比较四:

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

// 输出结果为:false

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

比较五:

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 不是同一个对象为 falses2.intern() == s3 是同一个对象为 true

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