理解 Java String
类的 equals
方法 —— 从源码到优化机制
Java 中的 String
类是最常用的类之一,而 equals
方法是用于比较两个字符串是否相等的重要工具。
本文将从源码入手,逐步解析 String
的 equals
方法,同时解答一些常见疑问,希望这篇文章能帮助初学者理解 String
的底层实现。
1. equals
方法源码解析
先看一下 String
类的 equals
方法源码(基于 Java 16+):
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
return (anObject instanceof String aString)
&& (!COMPACT_STRINGS || this.coder == aString.coder)
&& StringLatin1.equals(value, aString.value);
}
这段代码看起来不算复杂,但实际上涉及很多知识点,我们逐句来拆解。
2. 分步解析 equals
方法
2.1 if (this == anObject)
if (this == anObject) {
return true;
}
- 作用:判断当前对象
this
和传入的对象anObject
是否是同一个引用。- 如果它们是同一个对象(引用相等),直接返回
true
。 - 因为引用相等的两个对象,内容必然相等,这里直接返回,避免不必要的比较。
- 如果它们是同一个对象(引用相等),直接返回
2.2 (anObject instanceof String aString)
return (anObject instanceof String aString)
-
作用:
- 检查
anObject
是否是String
类型。如果不是,直接返回false
,因为一个String
对象不可能和其他类型的对象相等。 - Java 16 的新特性:模式匹配。它不仅检查类型,还完成了类型转换,并将转换后的
String
对象赋值给局部变量aString
,供后续代码使用。
- 检查
-
问题:
- 既然已经进行String类型判断了,为什么还要进行类型转换?
- 即使已经通过
instanceof
检查,编译器仍然认为anObject
是Object
类型,而Object
类型没有String
的字段或方法。 - 类型转换是告诉编译器:这个对象现在可以被安全地当作
String
类型使用。
- 即使已经通过
- 既然已经进行String类型判断了,为什么还要进行类型转换?
2.3 && (!COMPACT_STRINGS || this.coder == aString.coder)
&& (!COMPACT_STRINGS || this.coder == aString.coder)
-
背景:
- 在 Java 9 之后,引入了紧凑字符串机制(Compact Strings)来优化内存使用。
- 紧凑字符串机制根据字符串内容选择适合的编码方式:
- LATIN1:单字节编码(适用于 ASCII 范围内的字符)。
- UTF16:双字节编码(适用于更广范围的 Unicode 字符)。
coder
字段用来记录字符串的编码类型:0
表示 LATIN1。1
表示 UTF16。
-
作用:
- 如果启用了紧凑字符串(
COMPACT_STRINGS
为true
),必须检查两字符串的编码类型是否相同(this.coder == aString.coder
),否则它们不可能相等。 - 如果未启用紧凑字符串(
COMPACT_STRINGS
为false
),跳过编码检查。
- 如果启用了紧凑字符串(
2.4 && StringLatin1.equals(value, aString.value)
&& StringLatin1.equals(value, aString.value);
-
背景:
- 在 Java 9 的紧凑字符串机制中,
String
的底层存储由char[]
改为byte[]
,并通过编码类型(coder
)选择适当的操作方法。 StringLatin1.equals
是一个内部工具类,用于比较 LATIN1 编码字符串的字节数组内容。
- 在 Java 9 的紧凑字符串机制中,
-
作用:
- 调用
StringLatin1.equals
方法,对两个字符串的底层字节数组逐字节比较,确保内容一致。
- 调用
3. 完整逻辑总结
整个 equals
方法可以用以下逻辑描述:
- 如果两个字符串引用相同(
this == anObject
),直接返回true
。 - 如果
anObject
不是String
类型,返回false
。 - 如果启用了紧凑字符串机制(Compact Strings),检查两字符串的编码类型是否一致。
- 最后,通过字节数组比较字符串的内容是否一致。
4. 初学者常见问题解答
4.1 为什么 instanceof
判断了类型还要强制转换?
instanceof
判断只验证了运行时类型,但编译器仍然认为 anObject
是 Object
类型。为了使用 String
的字段(如 coder
)和方法,需要将 anObject
转换为 String
类型。
通过 Java 16 的模式匹配,instanceof
和强制转换合并到了一起,既简化了代码,又保证了类型安全。
4.2 紧凑字符串(Compact Strings)是什么?
紧凑字符串是 Java 9 引入的一种优化机制:
- 如果字符串内容可以用单字节编码(LATIN1),就用 1 个字节存储。
- 如果字符串内容需要双字节编码(UTF16),就用 2 个字节存储。
- 好处:
- 大幅减少内存占用(特别是对英文字符串)。
- 保证性能(通过
coder
字段快速判断编码)。
5. 示例代码和执行流程
示例代码
public class StringDemo {
public static void main(String[] args) {
String str1 = "hello";
String str2 = new String("hello");
System.out.println(str1.equals(str2)); // true
}
}
执行流程
str1
和str2
的引用不同,但内容相同。- 调用
str1.equals(str2)
:- 检查
str1
和str2
的引用:不同。 - 检查
str2
的类型:是String
。 - 检查编码类型(
coder
):相同(LATIN1)。 - 比较内容:逐字节比较
value
数组,内容一致。
- 检查
- 返回
true
。
6. 小结
String
的 equals
方法在逻辑上看似简单,但其实现充分考虑了性能和内存优化:
- 引入紧凑字符串机制(Compact Strings),减少内存占用。
- 通过
coder
字段快速判断编码类型。 - 借助内部工具类高效比较字节数组。
同时,Java 16 的模式匹配极大简化了类型判断和转换的逻辑,让代码更简洁。如果你能理解 equals
方法的实现,就已经掌握了 Java 字符串的核心机制!