基本数据类型只能使用 **== 不能使用equals,**用来比较值是否相同。引用数据类型时,==比较的是内从地址,如果该引用类型的类没有重写equals时候,比较的也是内存地址,只有当引用类型的类重写equals和hasecode方法之后,使用equals时会根据重写的equals进行比较,如String、Integer类的时候就是比较其中的值是否相同。
<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">==</font>永远比较内存地址, <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">equals()</font>默认也如此,但可被重写为比较内容。
一、核心区别总览
| 特性 | == 操作符 |
equals() 方法 |
|---|---|---|
| 本质 | 比较运算符(操作符) | 对象方法(可被重写) |
| 比较内容 | 内存地址(引用相等) | 对象内容(逻辑相等,可自定义) |
| 默认行为 | 总是比较内存地址 | Object类默认比较内存地址(同==),但可被重写 |
| 适用类型 | 基本类型 和 引用类型 | 只能用于引用类型 |
| 性能 | 极快(直接比较地址) | 可能较慢(需要字段比较) |
| null安全 | null == null 返回 true |
obj.equals(null) 应返回 false |
| 对称性 | 永远对称(a == b ⇔ b == a) |
取决于实现,应保证对称 |
二、详细对比分析
1. 基本类型(Primitive Types)的对比
java
public class PrimitiveComparison {
public static void main(String[] args) {
int a = 100;
int b = 100;
int c = 200;
// 对于基本类型,== 比较的是值
System.out.println("=== 基本类型比较(只能用 ==) ===");
System.out.println("a == b: " + (a == b)); // true(值相同)
System.out.println("a == c: " + (a == c)); // false(值不同)
// 基本类型没有equals()方法!
// System.out.println(a.equals(b)); // 编译错误!
// char类型
char ch1 = 'A';
char ch2 = 'A';
char ch3 = 'B';
System.out.println("ch1 == ch2: " + (ch1 == ch2)); // true
System.out.println("ch1 == ch3: " + (ch1 == ch3)); // false
// 浮点数比较(注意精度)
double d1 = 0.1 + 0.2;
double d2 = 0.3;
System.out.println("\n浮点数精度问题:");
System.out.println("0.1 + 0.2 == 0.3: " + (d1 == d2)); // false!
System.out.println("实际值:");
System.out.println("0.1 + 0.2 = " + d1);
System.out.println("0.3 = " + d2);
// 正确比较浮点数的方法
double epsilon = 0.0000001;
System.out.println("容差比较: " + (Math.abs(d1 - d2) < epsilon)); // true
}
}
2. 引用类型(Reference Types)的对比
java
public class ReferenceComparison {
public static void main(String[] args) {
System.out.println("=== 引用类型比较 ===");
// 创建两个不同的对象,内容相同
Object obj1 = new Object();
Object obj2 = new Object();
Object obj3 = obj1; // obj3指向obj1的同一对象
System.out.println("\n1. 使用 == 比较(内存地址):");
System.out.println("obj1 == obj2: " + (obj1 == obj2)); // false(不同对象)
System.out.println("obj1 == obj3: " + (obj1 == obj3)); // true(同一对象)
System.out.println("\n2. 使用equals()比较(Object类默认实现):");
System.out.println("obj1.equals(obj2): " + obj1.equals(obj2)); // false
System.out.println("obj1.equals(obj3): " + obj1.equals(obj3)); // true
// 结论:对于未重写equals()的类,== 和 equals() 效果相同!
}
}
3. String类的特殊案例(最经典的例子)
java
public class StringComparison {
public static void main(String[] args) {
System.out.println("=== String类比较(String重写了equals()) ===");
// 情况1:字符串字面量(享元模式)
String s1 = "hello";
String s2 = "hello";
System.out.println("\n1. 字符串字面量:");
System.out.println("s1 == s2: " + (s1 == s2)); // true(字符串常量池)
System.out.println("s1.equals(s2): " + s1.equals(s2)); // true
// 情况2:new String()创建
String s3 = new String("hello");
String s4 = new String("hello");
System.out.println("\n2. new String()创建:");
System.out.println("s3 == s4: " + (s3 == s4)); // false(不同对象)
System.out.println("s3.equals(s4): " + s3.equals(s4)); // true(内容相同)
// 情况3:混合比较
System.out.println("\n3. 字面量和new String()比较:");
System.out.println("s1 == s3: " + (s1 == s3)); // false
System.out.println("s1.equals(s3): " + s1.equals(s3)); // true
// 情况4:intern()方法
String s5 = s3.intern();
System.out.println("\n4. 使用intern()方法:");
System.out.println("s1 == s5: " + (s1 == s5)); // true(都指向常量池)
}
}
4. 包装类(Wrapper Classes)的对比
java
public class WrapperComparison {
public static void main(String[] args) {
System.out.println("=== 包装类比较(Integer缓存机制) ===");
// 情况1:自动装箱(缓存范围内)
Integer a = 127;
Integer b = 127;
System.out.println("\n1. 缓存范围内(-128~127):");
System.out.println("a == b: " + (a == b)); // true(缓存同一对象)
System.out.println("a.equals(b): " + a.equals(b)); // true
// 情况2:自动装箱(缓存范围外)
Integer c = 128;
Integer d = 128;
System.out.println("\n2. 缓存范围外(128陷阱):");
System.out.println("c == d: " + (c == d)); // false(不同对象)
System.out.println("c.equals(d): " + c.equals(d)); // true
// 情况3:new创建(总是新对象)
Integer e = new Integer(100);
Integer f = new Integer(100);
System.out.println("\n3. new Integer()创建:");
System.out.println("e == f: " + (e == f)); // false(不同对象)
System.out.println("e.equals(f): " + e.equals(f)); // true
// 情况4:与基本类型比较(自动拆箱)
int g = 100;
Integer h = 100;
System.out.println("\n4. 包装类与基本类型比较:");
System.out.println("g == h: " + (g == h)); // true(自动拆箱)
System.out.println("h.equals(g): " + h.equals(g)); // true(自动装箱比较)
// 注意:基本类型不能调用equals()
// System.out.println(g.equals(h)); // 编译错误!
}
}
三、equals()方法的重写机制
1. 为什么需要重写equals()?
java
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
// 未重写equals(),继承Object的默认实现(比较地址)
}
public class WhyOverrideEquals {
public static void main(String[] args) {
Person p1 = new Person("张三", 20);
Person p2 = new Person("张三", 20);
System.out.println("未重写equals()的问题:");
System.out.println("p1 == p2: " + (p1 == p2)); // false(合理)
System.out.println("p1.equals(p2): " + p1.equals(p2)); // false(不合理!)
System.out.println("\n我们期望:");
System.out.println("两个name和age相同的Person应该是相等的!");
}
}
2. 正确重写equals()的方法
java
import java.util.Objects;
class Student {
private String studentId;
private String name;
private int score;
public Student(String studentId, String name, int score) {
this.studentId = studentId;
this.name = name;
this.score = score;
}
// 正确重写equals()的步骤:
@Override
public boolean equals(Object obj) {
// 1. 检查是否是同一个对象
if (this == obj) return true;
// 2. 检查是否为null或类型不匹配
if (obj == null || getClass() != obj.getClass()) return false;
// 3. 类型转换
Student other = (Student) obj;
// 4. 逐个比较重要字段
// 使用Objects.equals()安全处理null
return Objects.equals(studentId, other.studentId) &&
Objects.equals(name, other.name) &&
score == other.score;
}
// 必须同时重写hashCode()!
@Override
public int hashCode() {
return Objects.hash(studentId, name, score);
}
}
public class CorrectEqualsExample {
public static void main(String[] args) {
Student s1 = new Student("1001", "李四", 90);
Student s2 = new Student("1001", "李四", 90);
Student s3 = new Student("1002", "王五", 85);
System.out.println("重写equals()后:");
System.out.println("s1.equals(s2): " + s1.equals(s2)); // true
System.out.println("s1.equals(s3): " + s1.equals(s3)); // false
// 验证hashCode一致性
System.out.println("\nhashCode验证:");
System.out.println("s1.hashCode() == s2.hashCode(): " +
(s1.hashCode() == s2.hashCode())); // true
// 在集合中使用
java.util.HashSet<Student> set = new java.util.HashSet<>();
set.add(s1);
set.add(s2); // 不会被添加(equals()返回true)
System.out.println("HashSet大小: " + set.size()); // 1
}
}
四、equals()方法的契约(必须遵守!)
java
/*
* equals()方法必须满足的五大特性:
*
* 1. 自反性 (Reflexive): x.equals(x) 必须返回 true
* 2. 对称性 (Symmetric): 如果 x.equals(y) 返回 true,则 y.equals(x) 也必须返回 true
* 3. 传递性 (Transitive): 如果 x.equals(y) 返回 true,y.equals(z) 返回 true,则 x.equals(z) 也必须返回 true
* 4. 一致性 (Consistent): 多次调用 equals(),只要对象没修改,结果应该一致
* 5. 非空性 (Non-null): x.equals(null) 必须返回 false
*/
public class EqualsContract {
static class BadEquals {
private String value;
// 错误示例:违反对称性
@Override
public boolean equals(Object obj) {
if (obj instanceof String) { // 错误!允许与String比较
return value.equals(obj);
}
if (obj instanceof BadEquals) {
return value.equals(((BadEquals) obj).value);
}
return false;
}
}
public static void main(String[] args) {
BadEquals bad = new BadEquals();
bad.value = "test";
String str = "test";
System.out.println("违反对称性的例子:");
System.out.println("bad.equals(str): " + bad.equals(str)); // true
System.out.println("str.equals(bad): " + str.equals(bad)); // false!
System.out.println("这违反了对称性!");
}
}
五、实际开发中的最佳实践
1. 选择比较方式的决策树
plain
要比较两个变量吗?
├── 是基本类型吗?
│ ├── 是 → 使用 ==
│ └── 否 → 是引用类型
│
└── 是引用类型吗?
├── 要比较对象引用吗?
│ ├── 是 → 使用 ==
│ └── 否 → 要比较对象内容
│
└── 要比较对象内容吗?
├── 是 → 使用 equals()
└── 注意:确保类正确重写了equals()
2. 各种场景的推荐做法
java
public class BestPractices {
public static void main(String[] args) {
System.out.println("=== 各种场景的最佳实践 ===");
// 场景1:字符串比较
String input = getUserInput();
// ❌ 错误做法
if (input == "admin") { ... }
// ✅ 正确做法
if ("admin".equals(input)) { ... } // 还能避免NPE!
// 场景2:枚举比较
enum Status { ACTIVE, INACTIVE }
Status status = getStatus();
// ✅ 枚举可以用 ==(枚举值保证单例)
if (status == Status.ACTIVE) { ... }
// 也可以用 equals()
if (Status.ACTIVE.equals(status)) { ... }
// 场景3:BigDecimal比较(财务计算)
java.math.BigDecimal price1 = new java.math.BigDecimal("10.00");
java.math.BigDecimal price2 = new java.math.BigDecimal("10.0");
// ❌ 错误做法
System.out.println("price1 == price2: " + (price1 == price2)); // false
// ✅ 正确做法
System.out.println("price1.equals(price2): " + price1.equals(price2)); // false(精度不同)
System.out.println("price1.compareTo(price2): " + price1.compareTo(price2)); // 0(值相同)
// 场景4:集合中的对象比较
java.util.List<String> list1 = java.util.Arrays.asList("A", "B");
java.util.List<String> list2 = java.util.Arrays.asList("A", "B");
System.out.println("\n集合比较:");
System.out.println("list1 == list2: " + (list1 == list2)); // false
System.out.println("list1.equals(list2): " + list1.equals(list2)); // true
// 场景5:数组比较
int[] arr1 = {1, 2, 3};
int[] arr2 = {1, 2, 3};
System.out.println("\n数组比较:");
System.out.println("arr1 == arr2: " + (arr1 == arr2)); // false
System.out.println("arr1.equals(arr2): " + arr1.equals(arr2)); // false!
System.out.println("Arrays.equals(arr1, arr2): " + java.util.Arrays.equals(arr1, arr2)); // true
}
static String getUserInput() { return "admin"; }
static Status getStatus() { return Status.ACTIVE; }
enum Status { ACTIVE, INACTIVE }
}
java
public class DefensiveProgramming {
// 方法1:使用Objects.equals()处理null
public static boolean safeCompare(Object a, Object b) {
return java.util.Objects.equals(a, b);
}
// 方法2:常量在前避免NPE
public static boolean checkPassword(String input) {
// ✅ 好的做法:常量在前
return "secret".equals(input);
// ❌ 不好的做法:可能NPE
// return input.equals("secret");
}
// 方法3:比较前检查类型
public static boolean compareSafely(Object obj1, Object obj2) {
if (obj1 == obj2) return true; // 包括都为null的情况
if (obj1 == null || obj2 == null) return false; // 一个为null
return obj1.equals(obj2);
}
public static void main(String[] args) {
String str1 = null;
String str2 = "hello";
System.out.println("安全比较示例:");
System.out.println("safeCompare(null, null): " + safeCompare(null, null)); // true
System.out.println("safeCompare(str1, str2): " + safeCompare(str1, str2)); // false
System.out.println("safeCompare(\"hello\", \"hello\"): " + safeCompare("hello", "hello")); // true
System.out.println("\n避免NPE:");
System.out.println("checkPassword(null): " + checkPassword(null)); // false(不会NPE)
}
}
六、常见面试问题
Q1:== 和 equals() 的根本区别是什么?
A :== 是比较内存地址(物理相等),equals() 是比较对象内容(逻辑相等,可自定义)。
Q2:String的 == 和 equals() 有什么区别?
A :String的 == 比较对象引用,equals() 比较字符串内容。字符串字面量可能共享同一个对象。
Q3:为什么重写equals()必须同时重写hashCode()?
A:这是Java的契约。如果两个对象equals()返回true,它们的hashCode()必须相同,否则在使用HashMap、HashSet等集合时会出现问题。
Q4:Integer的 == 比较有什么特殊之处?
A :Integer有缓存机制(-128~127),这个范围内的值用 == 比较可能返回true(同一对象),范围外返回false。
Q5:如何正确比较浮点数?
A :不要用 == 直接比较浮点数(精度问题),应该使用容差比较或BigDecimal。
七、总结表格
| 比较维度 | == |
equals() |
|---|---|---|
| 类型 | 操作符 | 方法 |
| 基本类型 | 比较值 | 不可用 |
| 引用类型默认 | 比较内存地址 | 比较内存地址(同==) |
| 引用类型重写后 | 仍比较内存地址 | 比较自定义内容 |
| String | 比较是否同一对象 | 比较字符串内容 |
| 包装类(缓存内) | 可能true(同一对象) | 比较值 |
| 包装类(缓存外) | false(不同对象) | 比较值 |
| null处理 | null == null为true |
obj.equals(null)应返回false |
| 性能 | O(1) | 可能O(n) |
| 集合中使用 | 不适合 | 必须正确实现 |
| 最佳实践 | 基本类型、枚举比较 | 引用类型内容比较 |
黄金法则:
- 基本类型 → 用
== - 引用类型比较引用 → 用
== - 引用类型比较内容 → 用
equals()(确保类正确重写) - String比较 → 永远用
equals()(除非明确要比较引用) - 包装类比较 → 永远用
equals()(避免缓存陷阱) - 浮点数比较 → 不要用
<font style="color:#DF2A3F;">==</font>,用容差或BigDecimal - 重写equals() → 必须同时重写hashCode()