一句话概括:
- 是一个运算符,用于比较两个变量的值是否相等,对于基本数据类型,比较的是值本身;对于引用数据类型,比较的是它们所指向对象的内存地址(即是否是同一个对象)。
equals()是一个方法,用于比较两个对象的(逻辑上)是否相等,它的默认行为(继承自Object类)与 相同,即比较内存地址,但很多类(如String,Integer)都重写了此方法,以实现自定义的“内容相等”逻辑。
详细对比
| 特性 | (运算符) | equals() (方法) |
|---|---|---|
| 本质 | Java语言提供的二元运算符 | Object 类中定义的实例方法 |
| 比较对象 | - 基本数据类型 ( int, double 等)- 引用数据类型 (任何对象) |
只能比较对象,如果传入基本数据类型,会先进行自动装箱。 |
| - 基本类型:比较变量的值是否相等。 - 引用类型:比较两个变量引用的内存地址是否相同(即是否指向同一个对象实例)。 |
默认情况下(Object.equals()),与 相同,比较内存地址。但可以被重写,以比较对象的或状态是否逻辑上相等。 |
|
| 能否被重写 | 不能。 是Java语言固有的运算符,其行为是固定的。 | 可以,任何类都可以重写 equals() 方法来定义自己的“相等”规则。 |
代码示例与分步解析
基本数据类型
对于基本数据类型, 比较的是它们的值。
int a = 10; int b = 10; int c = 20; System.out.println(a == b); // 输出: true (值都是10) System.out.println(a == c); // 输出: false (值不同) // 对于基本类型,调用 equals() 是不允许的,编译会报错 // a.equals(b); // Compile Error: int cannot be dereferenced
基本数据类型, 比较值,equals() 不适用。
引用数据类型(未重写 equals() 的情况)
当我们创建一个自定义类时,如果没有重写 equals() 方法,那么它使用的是 Object 类中的默认实现,这个实现和 是完全一样的——比较内存地址。
class Person {
private String name;
public Person(String name) {
this.name = name;
}
// 注意:我们没有重写 equals() 方法
}
public class Main {
public static void main(String[] args) {
Person p1 = new Person("张三");
Person p2 = new Person("张三");
Person p3 = p1;
System.out.println("p1 == p2 ? " + (p1 == p2)); // 输出: false
// p1 和 p2 是两个不同的对象,内存地址不同
System.out.println("p1.equals(p2) ? " + (p1.equals(p2))); // 输出: false
// equals() 默认行为,比较内存地址,与 == 相同
System.out.println("p1 == p3 ? " + (p1 == p3)); // 输出: true
// p3 指向 p1 的内存地址,是同一个对象
System.out.println("p1.equals(p3) ? " + (p1.equals(p3))); // 输出: true
// equals() 比较内存地址,与 == 相同
}
}
对于未重写 equals() 的自定义类, 和 equals() 的行为完全一致,都用于判断两个引用是否指向同一个对象。
引用数据类型(已重写 equals() 的情况)
很多Java核心类库中的类都重写了 equals() 方法,使其能够比较对象的内容。
经典案例:String 类
String 类重写了 equals() 方法,用来比较两个字符串对象的内容(字符序列)是否相同,而不管它们的内存地址是否相同。
String s1 = new String("hello");
String s2 = new String("hello");
String s3 = s1;
System.out.println("s1 == s2 ? " + (s1 == s2)); // 输出: false
// s1 和 s2 是两个不同的对象,内存地址不同
System.out.println("s1.equals(s2) ? " + (s1.equals(s2))); // 输出: true
// String 重写了 equals(),比较的是字符串内容 "hello" 是否相同
System.out.println("s1 == s3 ? " + (s1 == s3)); // 输出: true
// s3 指向 s1 的内存地址
System.out.println("s1.equals(s3) ? " + (s1.equals(s3))); // 输出: true相同,地址也相同
另一个案例:Integer 包装类
Integer i1 = new Integer(100);
Integer i2 = new Integer(100);
Integer i3 = 100; // 使用了缓存机制 (JDK 1.7+)
Integer i4 = 100;
Integer i5 = 200;
Integer i6 = 200;
System.out.println("i1 == i2 ? " + (i1 == i2)); // 输出: false (new 创建的对象地址不同)
System.out.println("i1.equals(i2) ? " + (i1.equals(i2))); // 输出: true (值相同)
System.out.println("i3 == i4 ? " + (i3 == i4)); // 输出: true (值在-128到127之间,会使用缓存,是同一个对象)
System.out.println("i3.equals(i4) ? " + (i3.equals(i4))); // 输出: true
System.out.println("i5 == i6 ? " + (i5 == i6)); // 输出: false (值超出缓存范围,是new的对象)
System.out.println("i5.equals(i6) ? " + (i5.equals(i6))); // 输出: true (值相同)
重写 equals() 的最佳实践
如果你想让你的自定义类支持基于内容的比较(在 HashSet, HashMap 或 List.contains() 中使用),你就必须重写 equals() 方法。
根据《Effective Java》的指导,一个好的 equals() 方法应该遵循以下规则,并重写 hashCode() 方法:
- 自反性:
x.equals(x)必须返回true。 - 对称性:
x.equals(y)和y.equals(x)必须返回相同的值。 - 传递性:
x.equals(y)为true,且y.equals(z)为true,x.equals(z)也必须为true。 - 一致性:只要对象内容没有被修改,多次调用
x.equals(y)应该返回相同的值。 - 非空性:
x.equals(null)必须返回false。
示例:重写 Person 类的 equals()
import java.util.Objects;
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// IDE可以一键生成,这里手动展示
@Override
public boolean equals(Object o) {
// 1. 检查是否是同一个对象引用
if (this == o) return true;
// 2. 检查参数是否为null,或者是否属于同一个类
if (o == null || getClass() != o.getClass()) return false;
// 3. 类型转换
Person person = (Person) o;
// 4. 比较关键字段(使用Objects.equals可以避免NullPointerException)
return age == person.age && Objects.equals(name, person.name);
}
// 重写equals()后,必须重写hashCode(),以保证“相等的对象必须有相同的哈希码”
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
public class Main {
public static void main(String[] args) {
Person p1 = new Person("张三", 30);
Person p2 = new Person("张三", 30);
Person p3 = new Person("李四", 30);
System.out.println("p1.equals(p2) ? " + (p1.equals(p2))); // 输出: true (name和age都相同)
System.out.println("p1.equals(p3) ? " + (p1.equals(p3))); // 输出: false (name不同)
}
}
总结与何时使用
| 场景 | 使用 | 使用 equals() |
|---|---|---|
| 比较基本数据类型 | 总是使用 | 不适用 |
| 判断两个引用是否指向同一个对象 | 使用 | 也可以使用,但 更直观、性能更高 |
| 比较两个对象的内容是否逻辑上相等 | 不使用(除非你想判断是否是同一个对象) | 必须使用,前提是相关的类已经重写了此方法(如 String, Date 等) |
在集合框架(Set, Map)中 |
在 HashMap 的 put 和 get 方法中, 用于判断键的引用是否相同。 |
在 HashSet 的 add 方法或 List 的 contains 方法中,equals() (或 hashCode) 用于判断元素是否已存在。 |
黄金法则:
- 如果你想知道两个变量是否指向内存中的同一个东西,用 。
- 如果你想知道两个对象在逻辑上是否“相等”(两个字符串是否包含相同的字符序列),用
equals()。
