核心要点
-
vs
equals(): 这是 Java 中最常见的混淆点。- 比较的是变量中存储的值(内存地址)。
equals():比较的是(对于字符串来说,就是字符序列)。
-
String类的equals()方法:String类重写了Object类的equals()方法,专门用于比较两个字符串对象的内容是否完全相同(区分大小写)。 -
equalsIgnoreCase()方法:当你需要比较字符串内容是否相同,但不关心大小写时,应该使用这个方法。
运算符:比较内存地址
在 Java 中,所有对象变量都是引用类型,它们存储的不是对象本身,而是对象在堆内存中的地址。
让我们来看几个例子:
String str1 = "hello";
String str2 = "hello";
String str3 = new String("hello");
String str4 = str3;
System.out.println("str1 == str2: " + (str1 == str2)); // true
System.out.println("str1 == str3: " + (str1 == str3)); // false
System.out.println("str3 == str4: " + (str3 == str4)); // true
解释:
-
str1 == str2为true:这是因为 Java 为了优化性能,会对字符串字面量("hello")进行字符串驻留(String Interning),当编译器遇到"hello"时,它会在字符串常量池中查找是否存在这个字符串,如果存在,就直接引用;如果不存在,就创建一个新的并放入池中。str1和str2都指向了常量池中同一个"hello"对象,所以它们的内存地址(引用)是相同的。 -
str1 == str3为false:new String("hello")的含义是“强制在堆内存中创建一个新的字符串对象”,这个对象的内容是"hello",但它与常量池中的"hello"是两个完全不同的对象,拥有不同的内存地址。str1(指向常量池)和str3(指向堆内存)的引用地址不同。 -
str3 == str4为true:str4被直接赋值为str3,这意味着str4和str3指向了堆内存中同一个String对象,它们的引用地址自然相同。
如果你只想判断两个字符串变量是否指向同一个对象(同一个内存地址),用 ,但在绝大多数业务场景下,我们关心的是字符串内容是否一样,而不是它们是不是同一个对象。
equals() 方法:比较字符串内容
String 类重写了 Object 的 equals() 方法,使其专门用于比较字符串的字符序列是否完全一致。
String str1 = "hello";
String str2 = "hello";
String str3 = new String("hello");
System.out.println("str1.equals(str2): " + str1.equals(str2)); // true
System.out.println("str1.equals(str3): " + str1.equals(str3)); // true
解释:
str1.equals(str2)为true:虽然str1和str2是同一个对象(str1 == str2也为true),但equals()方法会逐个比较它们的字符,因为内容都是"hello",所以返回true。str1.equals(str3)为true:str1和str3是不同的对象(str1 == str3为false),但equals()方法不关心地址,只关心内容,因为它们的内容都是"hello",所以返回true。
equals() 方法的实现逻辑(简化版):
// 这是 Object 类中 equals() 的默认实现,比较地址
public boolean equals(Object obj) {
return (this == obj);
}
// String 类重写后的实现
public boolean equals(Object anObject) {
// 1. 先判断地址是否相同,如果相同,直接返回 true,提高效率
if (this == anObject) {
return true;
}
// 2. 判断参数是否是 String 类型的实例
if (anObject instanceof String) {
// 3. 强制转换为 String 类型
String anotherString = (String)anObject;
// 4. 比较两个字符串的长度,如果不同,直接返回 false
int n = value.length;
if (n == anotherString.value.length) {
// 5. 逐个字符进行比较
char v1[] = value;
char v2[] = anotherString.value;
for (int i = 0; i < n; i++) {
if (v1[i] != v2[i]) {
return false; // 只要有一个字符不同,就返回 false
}
}
return true; // 所有字符都相同,返回 true
}
}
return false;
}
equalsIgnoreCase() 方法:忽略大小写比较
当你需要比较字符串,但希望 "Hello" 和 "hello" 被认为是相同时,equalsIgnoreCase() 就派上用场了。
String s1 = "Hello World"; String s2 = "hello world"; String s3 = "HELLO WORLD"; System.out.println(s1.equals(s2)); // false,因为 'H' != 'h' System.out.println(s1.equalsIgnoreCase(s2)); // true,忽略大小写后内容相同 System.out.println(s1.equalsIgnoreCase(s3)); // true
equals() 和 equalsIgnoreCase() 的最佳实践
永远不要使用 来比较字符串的内容
这是 Java 开发中的一条黄金法则,除非你有非常特殊的需求(检查单例模式中是否返回了同一个实例),否则请坚持使用 equals() 或 equalsIgnoreCase()。
避免 NullPointerException (NPE)
如果你尝试在一个 null 对象上调用 equals() 方法,程序会抛出 NullPointerException。
String s = null; String t = "hello"; // s.equals(t); // 这行代码会抛出 NullPointerException
安全的写法:
为了避免 NPE,有两种常见的最佳实践:
-
将常量放在前面(推荐): 这是业界最推荐的做法,因为常量(
"hello")肯定不是null,equals()方法永远不会被null对象调用。String s = null; boolean isHello = "hello".equals(s); // 返回 false,不会抛出异常
-
使用
Objects.equals()(Java 7+):java.util.Objects工具类提供了一个静态的equals()方法,它内部已经处理了null检查,非常安全。import java.util.Objects; String s1 = null; String s2 = "hello"; String s3 = "hello"; System.out.println(Objects.equals(s1, s2)); // false,安全 System.out.println(Objects.equals(s2, s3)); // true // Objects.equals 内部逻辑类似于: // return (a == b) || (a != null && a.equals(b));
总结表格
| 方法 | 区分大小写 | 示例 ("Hello".equals(...)) |
空指针安全 (null.equals(...)) |
|
|---|---|---|---|---|
| 内存地址(引用) | N/A | "a" == new String("a") -> false |
NullPointerException |
|
equals() |
(字符序列) | 是 | "Hello".equals("hello") -> false |
NullPointerException |
equalsIgnoreCase() |
(字符序列) | 否 | "Hello".equalsIgnoreCase("hello") -> true |
NullPointerException |
Objects.equals() |
(字符序列) | 是 | Objects.equals("Hello", "hello") -> false |
安全,返回 false |
最佳实践代码示例
import java.util.Objects;
public class StringComparison {
public static void main(String[] args) {
String username = null; // 模拟从数据库或配置中获取的用户名
String inputUsername = "admin";
// 错误示范:会抛出 NullPointerException
// if (username.equals(inputUsername)) { ... }
// 正确示范 1:将常量放在前面
if ("admin".equals(username)) {
System.out.println("Welcome, admin!");
} else {
System.out.println("Unknown user.");
}
// 正确示范 2:使用 Objects.equals() (更通用)
if (Objects.equals(username, inputUsername)) {
System.out.println("Welcome, " + inputUsername + "!");
} else {
System.out.println("Authentication failed.");
}
// 忽略大小写比较
String systemName = "LINUX";
String userInput = "linux";
if (systemName.equalsIgnoreCase(userInput)) {
System.out.println("System recognized.");
}
}
} 