- 内容相同(值相同)
- 对象相同(内存地址相同)
内容相同(值相同)
这是最常见的需求,即判断两个字符串变量所包含的字符序列是否一模一样,在 Java 中,有三种主要方法可以实现,但它们的底层原理和行为有所不同。
String.equals() - 推荐使用
这是判断字符串内容是否相等的标准、最安全的方法。
-
原理:
equals()方法会逐个比较两个字符串对象中的每一个字符,如果所有字符都相同,并且长度也相同,则返回true,否则返回false。 -
特点:
- 区分大小写:
"Hello".equals("hello")会返回false。 - 内容敏感:只关心字符内容,不关心对象本身。
- 区分大小写:
-
示例:
String s1 = new String("hello"); String s2 = new String("hello"); String s3 = "hello"; String s4 = "hello"; System.out.println(s1.equals(s2)); // true,因为内容都是 "hello" System.out.println(s1.equals(s3)); // true,因为内容都是 "hello" System.out.println(s3.equals(s4)); // true,因为内容都是 "hello"
String.equalsIgnoreCase() - 忽略大小写的比较
当你需要比较字符串内容,但不希望大小写影响结果时,这个方法非常有用。
-
原理:与
equals()类似,但会比较字符的“大小写不敏感”版本。 -
示例:
String s1 = "Java"; String s2 = "java"; System.out.println(s1.equals(s2)); // false System.out.println(s1.equalsIgnoreCase(s2)); // true
String.compareTo() - 字典序比较
这个方法不仅判断内容是否相等,还比较它们在字典(Unicode码表)中的顺序。
-
原理:
- 如果两个字符串完全相同,返回
0。 - 如果调用字符串的字典顺序小于参数字符串,返回一个负整数。
- 如果调用字符串的字典顺序大于参数字符串,返回一个正整数。
- 如果两个字符串完全相同,返回
-
示例:
String s1 = "apple"; String s2 = "banana"; String s3 = "apple"; System.out.println(s1.compareTo(s2)); // 返回一个负数 (因为 'a' < 'b') System.out.println(s2.compareTo(s1)); // 返回一个正数 (因为 'b' > 'a') System.out.println(s1.compareTo(s3)); // 返回 0 (因为内容完全相同)
注意:虽然可以用
compareTo() == 0来判断内容是否相等,但可读性不如equals(),除非你需要比较顺序,否则推荐使用equals()。
对象相同(内存地址相同)
这个角度判断的是两个字符串变量是否指向内存中的同一个对象,这涉及到 Java 中一个非常核心的概念:字符串常量池 (String Pool)。
方法:String == - 比较的是引用(地址)
操作符用于比较两个基本数据类型的值是否相等,或者两个对象的引用(内存地址)是否指向同一个实例。
- 原理:检查两个变量是否存储在 JVM 的同一块内存地址上。
- 特点:
- :即使两个字符串内容完全一样,只要它们是不同的对象, 就会返回
false。 - 与字符串常量池密切相关。
- :即使两个字符串内容完全一样,只要它们是不同的对象, 就会返回
的行为详解(字符串常量池)
Java 为了优化性能和节省内存,引入了字符串常量池。
-
使用双引号创建字符串(字面量): 当你使用
"some string"这种方式创建字符串时,JVM 首先会检查字符串常量池中是否已经存在一个内容为"some string"的对象。- 如果存在:直接返回池中该对象的引用,不会创建新对象。
- 如果不存在:在池中创建一个新的字符串对象,并返回其引用。
-
使用
new关键字创建字符串: 当你使用new String("some string")时,JVM 总会在堆内存中创建一个新的字符串对象,而不管常量池中是否已存在相同内容的字符串,这个新对象的引用会被赋给变量。
的经典示例
// 场景1:使用双引号,内容相同
String s1 = "hello";
String s2 = "hello";
// s1 和 s2 都指向常量池中的同一个 "hello" 对象
System.out.println(s1 == s2); // 输出 true
// 场景2:一个用双引号,一个用 new
String s3 = "hello"; // 指向常量池中的 "hello"
String s4 = new String("hello"); // 在堆中创建了一个新的 "hello" 对象
// s3 指向池,s4 指向堆,地址不同
System.out.println(s3 == s4); // 输出 false
// 场景3:两个都用 new 创建
String s5 = new String("hello"); // 堆中对象1
String s6 = new String("hello"); // 堆中对象2
// s5 和 s6 都指向堆中不同的对象
System.out.println(s5 == s6); // 输出 false
// 场景4:intern() 方法
// s4.intern() 会去常量池查找,发现已有 "hello",于是返回常量池中该对象的引用
String s7 = s4.intern();
System.out.println(s3 == s7); // 输出 true,因为 s3 和 s7 都指向常量池中的同一个对象
总结与最佳实践
| 场景 | 目标 | 推荐方法 | 原因 |
|---|---|---|---|
| 判断字符串内容是否相等 | 比较字符序列 | String.equals() |
这是设计的初衷,准确、安全、可读性高。 |
| 判断字符串内容是否相等(忽略大小写) | 比较字符序列(不区分大小写) | String.equalsIgnoreCase() |
专门为此场景设计,代码清晰。 |
| 需要知道字符串的字典顺序 | 比较大小 | String.compareTo() |
不仅能判断是否相等,还能返回比较结果。 |
| 判断两个变量是否指向同一个对象 | 比较内存地址 | String == |
直接比较引用地址,速度快,但通常不是判断字符串内容的最佳选择。 |
核心结论:
在 99% 的情况下,当你想知道两个字符串是否“相同时”,你都应该使用
equals()方法,而不是 。用于判断对象身份,而
equals()用于判断对象状态(内容),对于字符串来说,我们几乎总是关心其内容。
陷阱:null 安全
equals() 方法有一个潜在的陷阱:如果调用它的字符串对象是 null,会抛出 NullPointerException。
String s1 = null; String s2 = "hello"; s1.equals(s2); // 抛出 NullPointerException
为了避免这个问题,可以采用以下两种更安全的方式:
-
反转比较顺序(利用
String的equals方法是null安全的):"hello".equals(s1); // 返回 false,不会报错
-
使用
Objects.equals()(Java 7+):这是最推荐、最安全的方式。import java.util.Objects; Objects.equals(s1, s2); // 如果任一参数为 null,安全地返回 false
