.
1. 前言
为什么在 Java 中比较字符串很重要?
在 Java 编程中,处理字符串(String)的场景非常多。比如检查用户名、比对表单输入值、验证 API 响应等,几乎所有场景都需要进行字符串比较。
此时,“如何正确比较字符串”是初学者容易卡住的点。尤其是如果不理解 == 运算符和 equals() 方法的区别,就会导致 产生意外结果的 bug。
不懂 “==” 与 “equals” 的区别很危险
例如,请看下面的代码。
String a = "apple";
String b = new String("apple");
System.out.println(a == b); // 結果: false
System.out.println(a.equals(b)); // 結果: true
看到这段代码的输出结果,很多人会感到惊讶。相同的字符串,== 返回 false,equals() 返回 true。这是因为 Java 将“字符串视为引用类型”,== 比较的是引用地址。
因此,正确地比较字符串直接关系到程序的可靠性和可读性。换句话说,只要掌握正确的方法,就能在源头上防止 bug 的产生。
本文将学习的内容
本文将系统地讲解 Java 中字符串的比较方法,从基础到进阶。我们将围绕以下疑问,面向初学者进行结构化说明。
==与equals()的区别是什么?- 如何在比较时忽略大小写?
- 如何进行字典序比较?
- 如何在与 null 比较时避免异常?
通过结合实际工作中有用的代码示例,掌握正确的字符串比较知识,让我们一起学会在项目中安全、可靠地使用字符串比较吧。
2. Java 中字符串的基础
字符串是“引用类型”
在 Java 中,String 类型不是基本类型(如 int、boolean),而是 “引用类型(Reference Type)”。这意味着 String 变量并不直接保存字符串数据本身,而是 引用堆内存中存放的字符串对象。
也就是说,若这样写:
String a = "hello";
String b = "hello";
a 与 b 引用了同一个字符串 "hello",因此 a == b 可能返回 true。但这依赖于 Java 的 字符串常量池(String Interning) 机制的优化。
字符串字面量与 new String() 的区别
在 Java 中,多次使用相同的字符串字面量时,这些字面量会被优化为同一个引用。也就是说,Java 在运行时会共享这些字符串,以提升内存效率。
String s1 = "apple";
String s2 = "apple";
System.out.println(s1 == s2); // true(同じリテラルなので同一参照)
相反,使用 new 关键字显式创建对象时,会生成一个全新的引用。
String s3 = new String("apple");
System.out.println(s1 == s3); // false(異なる参照)
System.out.println(s1.equals(s3)); // true(内容は同じ)
因此,== 用来 比较引用是否相同,而 equals() 用来 比较内容是否相同,两者的用途差别很大。
String 是“不可变(Immutable)”的类
另一个重要特性是,String 是 不可变(immutable) 的。也就是说,一旦创建了 String 对象,其内容就不能被修改。
例如这样写:
String original = "hello";
original = original + " world";
看起来像是给原来的 original 加上了字符串,实际上会生成一个新的 String 对象,并将其赋值给 original。
正是因为这种不可变性,String 才是线程安全的,并且在安全性、缓存优化等方面都有优势。
3. 字符串比较的方法
使用 == 运算符进行引用比较
== 比较的是字符串对象的引用(地址)。即使内容相同,只要是不同的对象,也会返回 false。
String a = "Java";
String b = new String("Java");
System.out.println(a == b); // false
在此例中,a 是字面量,b 是通过 new 创建的,两者引用不同,结果为 false。不要把它用于内容比较,需格外注意。
使用 equals() 方法进行内容比较
equals() 是 比较字符串内容的正确方式。在大多数场景下,推荐使用此方法。
String a = "Java";
String b = new String("Java");
System.out.println(a.equals(b)); // true
即使引用不同,只要内容相同,就会返回 true。
与 null 比较时的注意点
下面的代码可能会抛出 NullPointerException。
String input = null;
System.out.println(input.equals("test")); // 例外発生!
为避免这种情况,建议采用 常量在前、变量在后 的写法,即 常量.equals(变量)。
System.out.println("test".equals(input)); // false(安全)
使用 equalsIgnoreCase() 方法进行大小写不敏感比较
在需要忽略大小写的场景(如用户名、电子邮件)下,equalsIgnoreCase() 非常实用。
String a = "Hello";
String b = "hello";
System.out.println(a.equalsIgnoreCase(b)); // true
不过,需要注意的是,在 Unicode 中某些特殊情况(如土耳其语的 “İ”)可能会出现意料之外的行为,国际化时需额外考虑。
使用 compareTo() 方法进行字典序比较
compareTo() 按字典序比较两个字符串,返回以下整数值:
- 0:相等
- 负数:调用者字符串在前(更小)
- 正数:调用者字符串在后(更大)
String a = "apple"; String b = "banana"; System.out.println(a.compareTo(b)); // 負の値("apple"は"banana"より前)
该方法常用于字典序排序或过滤处理,也在 Collections.sort()、TreeMap 的键比较等内部实现中使用。
4. 实践使用示例
用户输入校验(登录功能)
最常见的场景之一是判断用户名和密码是否匹配。
String inputUsername = "Naohiro";
String registeredUsername = "naohiro";
if (registeredUsername.equalsIgnoreCase(inputUsername)) {
System.out.println("ログイン成功");
} else {
System.out.println("ユーザー名が一致しません");
}
像这个例子一样,如果想忽略大小写进行比较,就适合使用 equalsIgnoreCase()。
但是,从安全角度来看,密码比较应该区分大小写,所以要使用 equals()。

输入验证(表单处理)
例如,从下拉菜单或文本框输入的值检查,也会使用字符串比较。
String selectedOption = request.getParameter("plan");
if ("premium".equals(selectedOption)) {
System.out.println("プレミアムプランを選択しました。");
} else {
System.out.println("その他のプランです。");
}
这样,作为兼顾null检查的安全比较,"常量".equals(变量) 的形式在实际工作中经常被使用。用户输入并不一定总是存在值,因此这是为了防止NullPointerException的写法。
多个条件的分支处理(像switch一样使用)
如果想在条件分支中处理多个字符串候选项,通常会连续使用 equals()。
String cmd = args[0];
if ("start".equals(cmd)) {
startApp();
} else if ("stop".equals(cmd)) {
stopApp();
} else {
System.out.println("コマンドが不正です");
}
从Java 14开始,字符串的 switch 语句也可以正式使用了。
switch (cmd) {
case "start":
startApp();
break;
case "stop":
stopApp();
break;
default:
System.out.println("不明なコマンドです");
}
这样,字符串比较直接连接到逻辑的分支处理,因此需要准确理解。
与null比较导致的bug及其对策
常见的失败例子是,与null值比较导致应用崩溃的情况。
String keyword = null;
if (keyword.equals("検索")) {
// 例外発生:java.lang.NullPointerException
}
在这种情况下,通过像下面这样写,可以安全地进行比较。
if ("検索".equals(keyword)) {
System.out.println("検索実行");
}
或者,也可以先进行更严格的null检查。
if (keyword != null && keyword.equals("検索")) {
System.out.println("検索実行");
}
null安全的代码是提高鲁棒性的必备技能。
5. 性能和优化
字符串比较中的处理成本
equals() 或 compareTo() 一般被优化为高速运行,但内部是逐字符比较的,因此,处理长字符串或大量数据时会产生影响。特别是,在循环中多次与相同字符串比较时,可能会导致意外的性能下降。
for (String item : items) {
if (item.equals("keyword")) {
// 比較回数が多い場合、注意
}
}
通过String.intern()实现比较加速
使用Java的String.intern()方法,可以将相同内容的字符串注册到JVM的“字符串池”中,并共享引用。这样,利用它就可以使用 == 进行比较,从而在性能上获得优势。
String a = new String("hello").intern();
String b = "hello";
System.out.println(a == b); // true
但是,如果滥用字符串池,可能会压迫堆区域,因此应该限于有限的用途。
equalsIgnoreCase()的陷阱和替代方案
equalsIgnoreCase() 很方便,但比较时会发生大小写转换处理,因此比普通的equals()成本稍高。在性能要求严格的场合,已经统一为大写或小写的値进行比较会更快。
String input = userInput.toLowerCase();
if ("admin".equals(input)) {
// 高速化された比較
}
这样事先转换后再使用equals(),可以提高比较处理的效率。
StringBuilder / StringBuffer 的活用
在发生大量字符串连接的情况下,使用 String 的话,每次都会生成新对象,内存和CPU的负载会增加。包括比较处理混在其中的情况,连接或构建时使用 StringBuilder,比较时保持为 String 是最佳实践。
StringBuilder sb = new StringBuilder();
sb.append("user_");
sb.append("123");
String result = sb.toString();
if (result.equals("user_123")) {
// 比較処理
}
通过缓存和预处理实现加速的设计
如果多次进行与相同字符串的比较,缓存一次比较结果,或者使用映射(HashMap等)进行预处理,从而减少比较处理本身的手法也很有效。
Map<String, Runnable> commandMap = new HashMap<>();
commandMap.put("start", () -> startApp());
commandMap.put("stop", () -> stopApp());
Runnable action = commandMap.get(inputCommand);
if (action != null) {
action.run();
}
这样,equals() 进行的字符串比较就可以替换为一次Map搜索,可以同时提高可读性和性能。
6. 常见问题(FAQ)
Q1. ==和equals()的区别是什么?
A.
==是引用比较(即内存地址的一致性)。另一方面,equals()是比较字符串的内容。
String a = new String("abc");
String b = "abc";
System.out.println(a == b); // false(参照が異なる)
System.out.println(a.equals(b)); // true(内容は同じ)
因此,想比较字符串内容时,一定要使用equals()。
Q2. 使用equals()时,为什么会因null导致错误?
A.
对null调用方法时,会发生NullPointerException。
String input = null;
System.out.println(input.equals("test")); // 例外発生!
为了防止这个错误,像下面这样从常量侧进行比较的写法是安全的。
System.out.println("test".equals(input)); // false(安全)
Q3. 如何忽略大小写进行比较?
.A.
使用 equalsIgnoreCase() 方法时,可以忽略大小写进行比较。
String a = "Hello";
String b = "hello";
System.out.println(a.equalsIgnoreCase(b)); // true
但对于全角字符或某些特殊的 Unicode 字符,结果可能会出乎意料,需要注意。
Q4. 想要按顺序比较字符串时该怎么办?
A.
当需要检查字符串的字典顺序前后关系时,使用 compareTo()。
String a = "apple";
String b = "banana";
System.out.println(a.compareTo(b)); // 負の値("apple"は"banana"より前)
返回值的含义:
- 0 → 相等
- 负值 → 左侧在前
- 正值 → 左侧在后
可用于排序等处理。
Q5. 字符串比较需要记住的最佳实践是什么?
A.
- 内容比较 必须使用
equals() - 注意 null 安全性,建议使用
"常量".equals(变量)的形式 - 要 忽略大小写,可以使用
equalsIgnoreCase()或者事先进行toLowerCase()/toUpperCase()处理 - 在需要大量比较或提升速度的场景下,考虑使用
intern()或缓存设计 - 始终关注 可读性与安全性的平衡
7. 总结
Java 字符串比较关键在于“正确区分使用”
通过本文,我们系统地阐述了 Java 中字符串比较的基础、实践以及性能方面的内容。由于 Java 中 String 是引用类型,如果比较方式错误,往往会导致意外行为的情况并不少见。
尤其是 == 与 equals() 的区别,是初学者最容易混淆的点。正确理解并区分使用它们,是编写安全可靠代码的关键。
本文学习要点(检查清单)
==是比较引用(内存地址)的运算符equals()是比较字符串 内容 的方法(最安全)- 使用
equalsIgnoreCase()可以忽略大小写 - 使用
compareTo()可以进行字符串的字典顺序比较 - 使用
"常量".equals(变量)的顺序可以实现 null 安全比较 - 在关注性能时,
intern()或缓存设计也很有效
在实际工作中也能发挥作用的字符串比较知识
登录判定、输入验证、数据库查询、条件分支等,字符串比较是日常开发工作密切相关的要素。掌握这些知识可以防止 bug,确保代码按预期运行。
今后在编写处理字符串的代码时,请参考本文内容,选择最适合的比较方式。


