Java 中 == 与 equals() 详解
Java 中 ==
与 equals()
详解
文章目录
- Java 中 `==` 与 `equals()` 详解
- 为什么要关注 `==` 和 `equals()`?
- `==`和`equals`的区别
- `==`运算符
- `equals()`方法
- 关键区别
- 字符串比较的特殊性
- 自动装箱与缓存
- 自定义类中如何正确重写 `equals()`
- 数组比较是个坑!
- 编程建议
- 1. 基本类型 vs 对象类型
- 2. 字符串比较
- 3. 自定义类
- 4. 数组比较
- 5. 空指针防御
- 6. 包装类比较
- 总结
在 Java 编程中,“两个变量相等”这件事并不像表面看起来那么简单。
==
和
equals()
的行为常常令人迷惑,尤其是在处理字符串、集合、包装类以及自定义对象时。
本文将深入剖析两者的本质区别、使用场景和最佳实践,并带你避开那些常见的坑。
为什么要关注 ==
和 equals()
?
看下面的例子:
String s1 = new String("hello");
String s2 = new String("hello");System.out.println(s1 == s2); // false
System.out.println(s1.equals(s2)); // true
两个内容一样的字符串,用 ==
得到的是 false
,但用 equals()
却是 true
。为什么?
再比如:
Integer a = 100;
Integer b = 100;
System.out.println(a == b); // true(意外?)Integer x = 200;
Integer y = 200;
System.out.println(x == y); // false(更意外?)
这类问题在日常写代码中非常常见,理解清楚这两者的机制至关重要。
==
和equals
的区别
==
运算符
==
对于基本数据类型和引用类型的作用效果是不同的:
- 对基本数据类型(
int
,char
,boolean
等):比较它们的值是否相等。 - 对引用类型(如数组、
String
, 自定义类等):比较两个对象的内存地址(即是否为同一个对象)。
由于 Java 只有值传递,所以对于
==
来说,不管是比较基本数据类型,还是引用数据类型的变量,其本质比较的都是值,只是引用类型变量存的值是对象的地址。
equals()
方法
equals()
不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。equals()
方法存在于Object
类中,而Object
类是所有类的直接或间接父类,因此所有的类都有equals()
方法。
Object
类中的equals
方法:
public boolean equals(Object obj) {return (this == obj);
}
- 默认行为(未重写时):
Object
类的equals()
方法内部使用==
,比较对象的内存地址。 - 重写后:用于比较对象的内容是否逻辑相等。例如
String
、Integer
等类已重写equals()
方法。
关键区别
特性 | == | equals() |
---|---|---|
比较对象 | 基本类型值或对象的内存地址 | 对象的内容(需重写方法) |
默认行为 | 比较地址 | Object 类默认比较地址 |
可自定义 | 不可变 | 可重写以定义逻辑相等 |
字符串比较的特殊性
- 直接赋值的字符串(如
String s = "abc"
)会进入字符串常量池,相同内容的字符串指向同一对象。 new String("abc")
会创建新对象,即使内容相同,==
也会返回false
。
自动装箱与缓存
详见深入理解Java包装类:自动装箱拆箱与缓存池机制
-
Integer
、Long
等包装类在-128
到127
范围内会缓存对象,此时==
可能返回true
:Integer a = 127; Integer b = 127; System.out.println(a == b); // true(缓存范围内)Integer c = 128; Integer d = 128; System.out.println(c == d); // false(超出缓存范围)
自定义类中如何正确重写 equals()
- 如果重写
equals()
,必须同时重写hashCode()
,确保两个相等的对象返回相同的哈希码。 - 否则在使用
HashMap
、HashSet
等哈希表时会出现逻辑错误。
关于hashCode()
的详细内容可以查看:hashCode() 有什么用?
数组比较是个坑!
在 Java 中,数组是一种特殊的对象类型,它用于存储固定数量的同类型数据。它继承自 Object
类,且没有重写equals()
,因此:==
比较两个数组的内存地址(是否为同一个对象),equals()
使用的是Object
中的默认实现,效果等同于==
。
内容正确比较方式:
使用 Arrays.equals()
方法(一维数组)或 Arrays.deepEquals()
(多维数组)
import java.util.Arrays;int[] a = {1, 2, 3};
int[] b = {1, 2, 3};
System.out.println(Arrays.equals(a, b)); // true(内容相同)int[][] arr1 = {{1, 2}, {3, 4}};
int[][] arr2 = {{1, 2}, {3, 4}};
System.out.println(Arrays.deepEquals(arr1, arr2)); // true
编程建议
1. 基本类型 vs 对象类型
-
基本类型:直接使用
==
比较值。int a = 10, b = 10; System.out.println(a == b); // true
-
对象类型:
- 判断是否为同一对象:用
==
。 - 判断内容是否相等:用
equals()
(需确保已正确重写)。
- 判断是否为同一对象:用
2. 字符串比较
-
优先使用
equals()
:避免依赖字符串常量池。String s1 = new String("hello"); String s2 = "hello"; System.out.println(s1.equals(s2)); // true
-
空安全比较:将常量放在
equals()
左侧。String str = null; System.out.println("hello".equals(str)); // false(不会抛异常)
3. 自定义类
-
必须重写
equals()
和hashCode()
:@Override public boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;MyClass obj = (MyClass) o;return Objects.equals(field1, obj.field1) && Objects.equals(field2, obj.field2); }@Override public int hashCode() {return Objects.hash(field1, field2); }
4. 数组比较
-
使用
Arrays.equals()
或Arrays.deepEquals()
:int[] arr1 = {1, 2, 3}; int[] arr2 = {1, 2, 3}; System.out.println(Arrays.equals(arr1, arr2)); // true
5. 空指针防御
-
使用
Objects.equals()
:String s1 = null; String s2 = "abc"; System.out.println(Objects.equals(s1, s2)); // false
6. 包装类比较
-
直接使用
equals()
:避免依赖缓存机制。Integer a = 200, b = 200; System.out.println(a.equals(b)); // true
总结
==
:判断“物理相等”(基本类型值或对象地址)。equals()
:判断“逻辑相等”(对象内容,需重写方法)。- 始终根据需求选择正确的比较方式,并在自定义类中正确实现
equals()
和hashCode()
。