Java中equals()与hashCode()方法的区别与联系:正确使用指南

在Object类中存在Java的equals()和hashCode()方法。因此,每个Java类都会获得equals()和hashCode()的默认实现。在本文中,我们将详细介绍Java的equals()和hashCode()方法。

Java 的 equals() 方法是相等比较的函数

Object类中定义了equals()方法,示例如下:

public boolean equals(Object obj) {
        return (this == obj);
}

根据Java文档中equals()方法的说明,任何实现都应遵循以下原则:

  • 对于任何对象x,x.equals(x)应该返回true。
  • 对于任何两个对象x和y,当且仅当y.equals(x)返回true时,x.equals(y)应该返回true。
  • 对于多个对象x、y和z,如果x.equals(y)返回true且y.equals(z)返回true,那么x.equals(z)应该返回true。
  • 多次调用x.equals(y)应该返回相同的结果,除非在equals()方法实现中使用的任何对象属性被修改。
  • Object类的equals()方法实现只有在两个引用指向同一个对象时才返回true。

Java的hashCode()方法

Java对象的hashCode()是一个本地方法,它返回对象的整数哈希码值。hashCode()方法的一般约定是:

  • 多次调用hashCode()应该返回相同的整数值,除非在equals()方法中使用的对象属性被修改。
  • 在同一个应用程序的多次执行中,对象的哈希码值可能会改变。
  • 如果根据equals()方法两个对象相等,那么它们的哈希码必须相同。
  • 如果根据equals()方法两个对象不相等,它们的哈希码不一定要不同。它们的哈希码值可能相等也可能不相等。

equals()和hashCode()方法的重要性

在Java中,hashCode()和equals()方法被用于基于哈希表的实现来存储和检索数据。我在《Java中的HashMap工作原理》中已经详细解释了这一点。equals()和hashCode()的实现应遵循以下规则:

  • 如果o1.equals(o2),那么o1.hashCode() == o2.hashCode()应该始终为true。
  • 如果o1.hashCode() == o2.hashCode为true,并不意味着o1.equals(o2)将为true。

何时需要重写equals()和hashCode()方法?

当我们重写equals()方法时,几乎必须同时重写hashCode()方法,以避免我们的实现违反它们之间的约定。请注意,如果equals()和hashCode()的约定被违反,程序不会抛出任何异常,如果您不打算将该类用作哈希表键,则不会出现任何问题。如果打算将类用作哈希表的键,则必须同时重写equals()和hashCode()方法。让我们来看看当我们依赖默认的equals()和hashCode()方法的实现,并使用自定义类作为HashMap的键时会发生什么。

package com.Olivia.java;

public class DataKey {

	private String name;
	private int id;

        // getter and setter methods

	@Override
	public String toString() {
		return "DataKey [name=" + name + ", id=" + id + "]";
	}

}
package com.Olivia.java;

import java.util.HashMap;
import java.util.Map;

public class HashingTest {

	public static void main(String[] args) {
		Map<DataKey, Integer> hm = getAllData();

		DataKey dk = new DataKey();
		dk.setId(1);
		dk.setName("Pankaj");
		System.out.println(dk.hashCode());

		Integer value = hm.get(dk);

		System.out.println(value);

	}

	private static Map<DataKey, Integer> getAllData() {
		Map<DataKey, Integer> hm = new HashMap<>();

		DataKey dk = new DataKey();
		dk.setId(1);
		dk.setName("Pankaj");
		System.out.println(dk.hashCode());

		hm.put(dk, 10);

		return hm;
	}

}

当我们运行以上程序时,它会打印出null。这是因为Object类的hashCode()方法用于查找桶以寻找键。由于我们无法访问HashMap的键,并且我们正在重新创建键来检索数据,您会注意到两个对象的哈希码值是不同的,因此找不到值。

实现equals()和hashCode()方法

eclipse生成hashCode和equals方法
@Override
public int hashCode() {
	final int prime = 31;
	int result = 1;
	result = prime * result + id;
	result = prime * result + ((name == null) ? 0 : name.hashCode());
	return result;
}

@Override
public boolean equals(Object obj) {
	if (this == obj)
		return true;
	if (obj == null)
		return false;
	if (getClass() != obj.getClass())
		return false;
	DataKey other = (DataKey) obj;
	if (id != other.id)
		return false;
	if (name == null) {
		if (other.name != null)
			return false;
	} else if (!name.equals(other.name))
		return false;
	return true;
}

请注意,equals()和hashCode()方法都使用相同的字段进行计算,以确保它们的约定仍然有效。如果您再次运行测试程序,我们将从映射中获取对象,并且程序将打印10。我们还可以使用Project Lombok自动生成equals和hashCode方法的实现。

哈希冲突是什么?

简而言之,Java的哈希表实现使用以下逻辑进行获取和放置操作:

  1. 首先,使用”key”的哈希码识别要使用的”桶”(Bucket)。
  2. 如果桶中没有具有相同哈希码的对象,则将对象添加到放操作中,并为获取操作返回null。
  3. 如果桶中有其他具有相同哈希码的对象,则使用equals()方法:
    • 如果equals()返回true且为放操作,则覆盖对象值。
    • 如果equals()返回false且为放操作,则将新条目添加到桶中。
    • 如果equals()返回true且为取操作,则返回对象值。
    • 如果equals()返回false且为取操作,则返回null。
Java HashMap,Java中HashMap的工作原理

如果我们不实现hashCode()和equals()会怎么样?

我们已经在上面看到,如果没有实现hashCode()方法,我们将无法检索到值,因为HashMap使用哈希码来找到存储条目的桶。如果我们只实现hashCode()方法而没有实现equals()方法,同样也无法检索到值,因为equals()方法会返回false。

实现equals()和hashCode()方法的最佳实践

  • 在equals()和hashCode()方法实现中使用相同的属性,这样当任何属性更新时,它们的约定不会被违反。
  • 最好使用不可变对象作为哈希表键,这样我们可以缓存哈希码而不是在每次调用时计算它。这就是为什么String是哈希表键的良好候选,因为它是不可变的并且缓存了哈希码值。
  • 实现hashCode()方法,使哈希冲突的数量最少,并且条目均匀分布在所有桶中。

你可以从我们的GitHub仓库下载完整的代码。

bannerAds