Java 比较运算符详解:==、!=、<、> 与 equals() 的区别

目次

1. 本文你将学到的内容(关键要点先行)

在 Java 中,比较运算符是用于比较数值、字符等值的基础语言特性。
然而,许多初学者在比较 StringInteger 等对象 时会出现问题,尤其是错误地使用 == 运算符。

本节提前概括关键要点,帮助你快速了解 何时可以安全使用比较运算符——以及何时不可以

1.1 比较运算符分为两类

Java 的比较运算符可以归为两大类:

  • 关系运算符(顺序比较) <<=>>=
  • 相等运算符(等值比较) ==!=

在处理 基本类型(如 intdoublechar)时,这些运算符的行为正如你所预期的那样。

int a = 10;
int b = 20;

System.out.println(a < b);   // true
System.out.println(a == b);  // false

1.2 重要提示:== 并不总是比较值

这是 Java 中最常见的困惑来源。

  • 对于 基本类型== 比较的是实际的数值
  • 对于 引用类型(对象)== 比较的是 两个变量是否指向同一个对象

这一差异至关重要。

String s1 = new String("Java");
String s2 = new String("Java");

System.out.println(s1 == s2);      // false
System.out.println(s1.equals(s2)); // true

即使文本看起来相同,s1s2 在内存中是 不同的对象

👉 经验法则:
如果你想检查两个对象的内容是否相同,请使用 equals()

1.3 快速决策指南(速查表)

如果只能记住一件事,就记住下面这点:

  • 基本类型值(int、boolean、char 等) → 使用比较运算符(==<> 等)
  • 对象内容比较(String、Integer、自定义对象) → 使用 equals()Objects.equals()
  • 排序或比较大小(哪个更大/更小) → 使用 compareTo()Comparator

关键洞察:
== 看似简单,但对对象而言它回答的是
“这两个是同一个对象吗?”,而不是
“它们的值相同吗?”

1.4 阅读本文后你将能够做到的事

完成本文后,你将能够:

  • 正确且安全地使用 Java 比较运算符
  • 避免因 ==equals() 混用而导致的常见 bug
  • 理解 Integer 比较有时表现不一致的原因
  • 在比较值、对象或顺序时选择合适的方法

在下一节中,我们将先对 所有 Java 比较运算符 进行完整概览,然后深入探讨常见陷阱和最佳实践。

2. Java 比较运算符(完整列表)

本节将对 所有 Java 比较运算符 进行归类,并阐明 它们能比较什么、不能比较什么。目标是在进入更复杂的案例之前消除歧义。

2.1 比较运算符始终返回布尔值

Java 中的每个比较运算符都会返回 boolean 类型的值:

  • true → 条件成立
  • false → 条件不成立

这也是比较运算符常用于 ifwhile 等控制语句的原因。

int x = 5;
int y = 10;

boolean result = x < y;  // true

比较运算符 不会修改值——它们仅仅对条件求值。

2.2 关系运算符:<<=>>=

这些运算符用于比较 顺序或大小。它们主要用于数值类型和 char

OperatorMeaning
<less than
<=less than or equal to
>greater than
>=greater than or equal to

数字示例

int a = 10;
int b = 20;

System.out.println(a < b);   // true
System.out.println(a >= b);  // false

char 示例

字符比较是基于它们的 Unicode 值

char c1 = 'A';
char c2 = 'B';

System.out.println(c1 < c2); // true

注意:
这些运算符 不能用于 String。这样做会导致编译时错误。

2.3 Equality Operators: == and !=

相等运算符用于检查两个操作数是否相等。

OperatorMeaning
==equal to
!=not equal to

Safe usage with primitive types

原始类型的安全使用

int x = 5;
int y = 5;

System.out.println(x == y); // true
System.out.println(x != y); // false

在这里,Java 比较 实际值,这既直接又安全。

2.4 Comparing boolean Values

比较 boolean

boolean 值也可以使用 ==!= 进行比较。

boolean f1 = true;
boolean f2 = false;

System.out.println(f1 == f2); // false

然而,在实际代码中,写成下面的形式更易读:

if (isEnabled) {
    // do something
}

而不是:

if (isEnabled == true) { ... }

2.5 Types That Work Well with Comparison Operators

适用于比较运算符的类型

Safe to use comparison operators directly:

可以直接安全使用比较运算符的类型:

  • int , long , double , float
  • char
  • boolean (only == / != )

Not safe or not allowed:

不安全或不允许的类型:

  • String
  • Wrapper classes ( Integer , Long , etc.)
  • Custom objects

这些类型需要 不同的比较技术,我们将在下一节中介绍。

2.6 Key Takeaway from This Section

本节关键要点

  • 比较运算符始终返回 truefalse
  • 它们在 原始类型 上可靠工作
  • 将它们用于对象可能导致错误或编译失败

在下一节中,我们将重点关注 原始类型,此时比较运算符的行为正如预期。

3. Comparison Operators with Primitive Types (Safe Zone)

原始类型的比较运算符(安全区)

原始类型是比较运算符 最安全、最简单 的使用场景。
清晰地理解本节内容有助于你识别何时会出现更复杂的情况。

3.1 What Are Primitive Types?

什么是原始类型?

原始类型存储 实际值,而不是引用。

常见示例包括:

  • 数值类型:int , long , double , float
  • 字符类型:char
  • 布尔类型:boolean

由于不涉及对象引用,比较行为是可预测的。

3.2 Comparing Integers and Long Values

int a = 100;
int b = 100;

System.out.println(a == b); // true
System.out.println(a < b);  // false

Java 直接比较数值。

Mixed numeric types

int x = 10;
long y = 10L;

System.out.println(x == y); // true

Java 在比较之前会执行 自动类型提升

3.3 Comparing Characters (char)

虽然 char 表示字符,但 Java 在内部将其视为数字。

char c1 = 'A';
char c2 = 'a';

System.out.println(c1 < c2); // true

此比较基于 Unicode 值,而不是人类语言的字母顺序规则。

3.4 Comparing Boolean Values

boolean flag1 = true;
boolean flag2 = false;

System.out.println(flag1 != flag2); // true

在实际使用中,避免冗余的比较:

if (isLoggedIn) { ... }      // preferred
if (isLoggedIn == true) { } // unnecessary

3.5 Floating-Point Comparison Pitfall (double / float)

这是一种 经典的 Java 陷阱

double d1 = 0.1 + 0.2;
double d2 = 0.3;

System.out.println(d1 == d2); // may be false

浮点数的存储存在 精度限制

Recommended approach: use a tolerance (epsilon)

double epsilon = 0.000001;

if (Math.abs(d1 - d2) < epsilon) {
    // treat as equal
}

对于金融或高精度计算,建议使用 BigDecimal

3.6 Summary of the Safe Zone

安全区小结

  • 原始类型可以直接比较
  • char 比较使用 Unicode 值
  • 浮点数相等比较需要特别处理
  • 到目前为止,比较运算符的行为直观易懂

接下来,我们将进入 危险区
为什么在对象上使用 == 会导致意外结果。

4. Why Using == with Objects Causes Problems

为什么在对象上使用 == 会导致问题

这就是许多 Java 初学者——甚至是中级开发者——常常卡住的地方。
一旦开始使用 引用类型(对象)== 的行为就会改变。

4.1 == 比较对象引用,而不是内容

对于对象,== 运算符检查 两个变量是否指向内存中的同一个对象

String s1 = new String("Java");
String s2 = new String("Java");

System.out.println(s1 == s2); // false

即使两个字符串看起来相同,它们也是 不同的对象,所以比较会失败。

4.2 equals() 比较对象内容

equals() 方法旨在比较 逻辑相等性,即对象的实际内容。

System.out.println(s1.equals(s2)); // true

String 类重写了 equals(),使其比较字符序列,而不是内存地址。

黄金法则:

  • 同一个对象? → ==
  • 同一个值/内容? → equals()

4.3 为什么 == 有时在字符串字面量上有效

下面的例子会让很多开发者感到困惑:

String a = "Java";
String b = "Java";

System.out.println(a == b); // true

这发生是因为 字符串常量池

  • 字符串字面量存放在共享池中
  • 相同的字面量可能引用同一个对象

然而,这种行为是 实现细节,不应依赖它。

String x = "Java";
String y = new String("Java");

System.out.println(x == y);      // false
System.out.println(x.equals(y)); // true

👉 始终使用 equals() 来比较字符串内容。

4.4 nullequals() —— 另一个常见陷阱

null 引用上调用 equals() 会导致运行时错误。

String str = null;
str.equals("Java"); // NullPointerException

安全模式 1:在常量上调用 equals()

if ("Java".equals(str)) {
    // safe
}

安全模式 2:使用 Objects.equals()

if (Objects.equals(str, "Java")) {
    // safe and clean
}

4.5 本节小结

  • == 比较对象引用
  • equals() 比较内容
  • 字符串常量池的行为可能隐藏 bug
  • 始终考虑空安全

接下来,我们将看看另一个微妙的陷阱:
比较包装类,如 IntegerLong

5. 比较包装类(Integer、Long 等)

包装类看起来像数字,但它们仍然是 对象

5.1 什么是包装类?

包装类允许将基本类型的值当作对象来使用。

PrimitiveWrapper
intInteger
longLong
doubleDouble
booleanBoolean

5.2 为什么 == 会产生不一致的结果

Integer a = 100;
Integer b = 100;

System.out.println(a == b); // true
Integer x = 1000;
Integer y = 1000;

System.out.println(x == y); // false

这源于 Integer 缓存(通常在 -128 到 127 之间)。

== 的结果取决于 JVM 的内部行为,而不是值的相等性。

5.3 正确比较包装类的值

使用 equals() 进行值比较。

System.out.println(x.equals(y)); // true

5.4 自动装箱与拆箱的陷阱

Integer a = 100;
int b = 100;

System.out.println(a == b); // true

这能工作是因为 自动拆箱,但:

  • 如果 anullNullPointerException
  • 代码意图变得不明确

显式比较更安全。

5.5 推荐的比较模式

  • 值比较 → equals() / Objects.equals()
  • 空安全比较 → Objects.equals()
  • 引用比较 → ==(极少且有意使用)

5.6 本节小结

  • 包装类是引用类型
  • == 对值比较不可靠
  • 始终一致地使用 equals()

接下来,让我们关注 空安全比较技术

6. 空安全比较技术

空相关的 bug 在 Java 应用中极为常见。

6.1 使用 null 的基本规则

  • null == null → true
  • null 调用方法 → 运行时错误
  • null 使用关系运算符( < , > )→ 编译错误

6.2 危险模式

str.equals("Java"); // unsafe

6.3 安全模式 #1:常量在前

"Java".equals(str);

6.4 安全模式 #2:Objects.equals()

Objects.equals(str, "Java");

这在内部处理所有 null 情况。

6.5 何时使用 Objects.equals()

  • 比较变量
  • 可为空的值
  • 更简洁的条件逻辑

6.6 设计提示:减少 Null 使用

  • 提前初始化值
  • 在适当的地方使用 Optional
  • 减少 null → 更简单的比较

6.7 小节总结

  • 切勿在可为空的引用上调用方法
  • 优先使用空安全的比较工具
  • 设计时尽量减少 null 的使用

接下来,我们将介绍 使用 compareTo() 进行顺序比较

7. 使用 compareTo() 比较顺序

比较运算符无法确定 对象的顺序

7.1 compareTo() 是什么?

compareTo() 比较顺序并返回:

  • 负数 → 小于
  • 零 → 等于
  • 正数 → 大于

7.2 字符串顺序示例

String a = "Apple";
String b = "Banana";

if (a.compareTo(b) < 0) {
    System.out.println("Apple comes first");
}

7.3 包装类也支持 compareTo()

Integer x = 10;
Integer y = 20;

System.out.println(x.compareTo(y)); // negative

7.4 equals()compareTo()

  • 检查相等性 → equals()
  • 排序/排序 → compareTo()

7.5 与排序的关联

Collections.sort() 这样的方法在内部依赖 compareTo()

7.6 小节总结

  • 比较运算符无法比较对象的顺序
  • compareTo() 是正确的工具
  • 对于排序和有序集合至关重要

8. 常见错误(快速检查表)

8.1 对字符串使用 ==

str1 == str2
str1.equals(str2)

8.2 对包装类使用 ==

Integer a == b
a.equals(b)

8.3 直接比较浮点值

a == b
✅ use tolerance or BigDecimal

8.4 忘记空检查

obj.equals(x)
Objects.equals(obj, x)

8.5 对对象使用 <

str1 < str2
str1.compareTo(str2)

9. 最终总结:如何选择正确的比较方式

9.1 决策指南

  • 基本类型 → 比较运算符
  • 对象内容equals() / Objects.equals()
  • 顺序和排序compareTo() / Comparator

9.2 最佳实践

  • 理解 == 实际含义
  • 始终考虑空安全
  • 避免依赖 JVM 内部实现

9.3 接下来学习什么

  • 逻辑运算符( && , ||
  • ifswitch 语句
  • 用于自定义排序的 Comparator
  • 正确实现 equals() / hashCode()

常见问题

Q1. Java 中 ==equals() 有何区别?

== 比较对象的引用,而 equals() 比较内容。

Q2. 为什么 == 有时在字符串上有效?

因为字符串常量池。此行为不应依赖。

Q3. 比较可为空的值最安全的方式是什么?

使用 Objects.equals(a, b)

Q4. 如何按字母顺序比较字符串?

使用 compareTo()

Q5. Java 中比较运算符足够吗?

它们对基本类型足够,但对象需要使用 equals()compareTo()