String 类的比较主要有三种方式,它们各有用途,绝不能混为一谈:

- 运算符:比较引用地址(是否是同一个对象)。
equals()方法:比较(字符序列是否完全相同)。compareTo()方法:比较字符串字典序(按 Unicode 码值逐个字符比较)。
下面我们逐一深入讲解。
运算符
是 Java 中的一个关系运算符,用于比较两个变量所引用的对象内存地址是否相同。
核心要点:
- 比较的是“地址”,不是“内容”。
- 如果两个
String变量指向内存中的同一个String对象(即同一个地址),则 返回true。 - 如果它们指向不同的对象,即使内容完全一样, 也返回
false。
两种情况分析:
字面量赋值(String Pool / 字符串常量池)
当使用双引号 创建一个字符串时,JVM 会首先在字符串常量池中查找是否存在相同内容的字符串,如果存在,就直接引用该对象;如果不存在,则创建一个新的字符串对象并存入池中。

String s1 = "hello";
String s2 = "hello";
// s1 和 s2 都指向常量池中同一个 "hello" 对象
System.out.println(s1 == s2); // 输出: true
String s3 = "world";
String s4 = new String("world");
// s3 指向常量池中的 "world"
// s4 指向堆中 newly created 的 "world" 对象
System.out.println(s3 == s4); // 输出: false
new 关键字创建
使用 new 关键字创建字符串时,JVM 不会在字符串常量池中查找,而是直接在堆内存中创建一个新的 String 对象,即使内容已经存在。
String s5 = new String("java");
String s6 = new String("java");
// s5 和 s6 都是在堆中新创建的对象,地址不同
System.out.println(s5 == s6); // 输出: false
equals() 方法
equals() 是 Object 类的方法,String 类对其进行了重写,专门用于比较两个字符串的是否完全一致。
核心要点:
- 比较的是“内容”,不是“地址”。
- 它会逐个字符比较,只有当所有字符都相同时,才返回
true。 - 这是判断两个字符串内容是否相等的标准方法。
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
// s1 和 s2 内容相同
System.out.println(s1.equals(s2)); // 输出: true
// s1 和 s3 内容也相同,尽管地址不同
System.out.println(s1.equals(s3)); // 输出: true
// 注意:为了避免空指针异常,推荐使用 "常量.equals(变量)" 的形式
String s4 = null;
// System.out.println(s4.equals("hello")); // 这会抛出 NullPointerException
System.out.println("hello".equals(s4)); // 输出: false,更安全
// Java 7+ 引入了 Objects.equals(),可以更优雅地处理 null
// import java.util.Objects;
// System.out.println(Objects.equals(s4, "hello")); // 输出: false
compareTo() 方法
compareTo() 方法用于按字典序(lexicographical order)比较两个字符串,它基于字符的 Unicode 码值进行比较。

核心要点:
- 按字典序比较。
- 返回一个整型值,这个值的意义是:
- 返回
0:两个字符串内容完全相等。 - 返回负整数:调用此方法的字符串小于参数字符串。
- 返回正整数:调用此方法的字符串大于参数字符串。
- 返回
比较规则:
- 从左到右,逐个字符比较其 Unicode 码值。
- 在第一个不相等的字符处,比较结束,返回这两个字符码值的差值。
- 如果所有字符都相等,但一个字符串比另一个长,则返回长度差值。
- 如果所有字符都相等且长度也相等,则返回
0。
String s1 = "apple"; String s2 = "banana"; String s3 = "apple"; String s4 = "applet"; // 'a' 的 Unicode 码值小于 'b',"apple" < "banana" System.out.println(s1.compareTo(s2)); // 输出一个负数 ( -1) 完全相同 System.out.println(s1.compareTo(s3)); // 输出: 0 // "apple" 和 "applet" 前5个字符相同,但 "applet" 更长 System.out.println(s1.compareTo(s4)); // 输出一个负数 ( -1, 因为 't' 的码值) System.out.println(s4.compareTo(s1)); // 输出一个正数 ( 1) // 大小写敏感:'A' (65) 和 'a' (97) String s5 = "Apple"; System.out.println(s1.compareTo(s5)); // 输出一个正数 (97 - 65 = 32)
不区分大小写的比较:compareToIgnoreCase()
如果需要不区分大小写的字典序比较,可以使用 compareToIgnoreCase() 方法。
String s1 = "Apple"; String s2 = "apple"; System.out.println(s1.compareTo(s2)); // 输出: -32 (因为 'A' < 'a') System.out.println(s1.compareToIgnoreCase(s2)); // 输出: 0
总结与最佳实践
| 方法 | 用途 | 示例 | 结果 | |
|---|---|---|---|---|
| 判断是否为同一个对象 | 引用地址 | "a" == new String("a") |
false |
|
equals() |
是否相等 | 字符序列 | "a".equals(new String("a")) |
true |
compareTo() |
按字典序比较大小 | 字符序列的 Unicode 值 | "b".compareTo("a") |
正数 |
何时使用哪个?
-
判断是否为同一个对象实例:
- 在某些性能敏感的场景下,或者当你明确知道需要检查对象唯一性时,使用 。
- 示例:缓存系统中,检查缓存中是否已存在某个对象。
if (cache.get(key) == value) { // 缓命中,且是同一个对象 }
-
判断字符串内容是否相等(最常用):
- 99% 的情况下,当你想比较两个字符串是否“一样”时,都应该使用
equals()。 - 示例:用户登录、密码校验、配置项比对等。
if (username.equals("admin") && password.equals("123456")) { // ... } - 安全写法:为了避免
NullPointerException,推荐将常量放在前面。if ("admin".equals(username) && "123456".equals(password)) { // ... }
- 99% 的情况下,当你想比较两个字符串是否“一样”时,都应该使用
-
需要比较字符串大小顺序时:
- 当你需要对字符串进行排序(如按字母顺序、日期顺序等)时,使用
compareTo()。 - 示例:对一组单词进行字典序排序。
String[] words = {"banana", "apple", "cherry"}; Arrays.sort(words); // 内部会使用 compareTo() 方法进行比较
- 当你需要对字符串进行排序(如按字母顺序、日期顺序等)时,使用
常见误区
误区: 和 equals 没什么区别,哪个用着方便就用哪个。
真相:这是初学者最容易犯的错误,混淆这两者会导致难以发现的 bug,在登录功能中,如果误用 来比较用户输入的密码和数据库中存储的密码(即使内容相同,也可能是不同对象),会导致登录永远失败。
记住这个黄金法则:
- 想知道是否一样?用
equals()。 - 想知道是不是同一个东西(同一个内存地址)?用 。
