Java equals() 和 hashCode()

Java的equals()和hashCode()方法存在于Object类中。因此,每个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,x.equals(y)应返回true当且仅当y.equals(x)返回true
  • 对于多个对象x、y和z,如果x.equals(y)返回true并且y.equals(z)返回true,那么x.equals(z)应返回true
  • 多次调用x.equals(y)应返回相同的结果,除非被用于equals()方法实现的任何对象属性被修改。

Object类的equals()方法实现仅在两个引用都指向同一对象时才返回trueJava 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为真,则并不意味着o1.equals(o2)将为true

何时覆盖 equals() 和 hashCode() 方法?

当我们覆盖 equals() 方法时,几乎必须同时覆盖 hashCode() 方法,以确保我们的实现不违反它们的契约。请注意,如果 equals() 和 hashCode() 的契约被违反,程序不会抛出任何异常,如果你不打算将该类用作哈希表键,那么这不会创建任何问题。但如果打算将类用作哈希表键,则必须同时覆盖 equals() 和 hashCode() 方法。让我们看看当我们依赖 equals() 和 hashCode() 方法的默认实现并使用自定义类作为 HashMap 键时会发生什么。

package com.journaldev.java;

public class DataKey {

	private String name;
	private int id;

        // getter 和 setter 方法

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

}
package com.journaldev.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() 方法

我们可以定义自己的equals()和hashCode()方法实现,但如果我们不小心实现它们,可能会在运行时出现奇怪的问题。幸运的是,大多数IDE这些天提供了自动实现它们的方法,如果需要,我们可以根据我们的要求更改它们。我们可以使用Eclipse自动生成equals()和hashCode()方法。这是自动生成的equals()和hashCode()方法实现。

@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. 首先使用“键”哈希码标识要使用的“桶”。
  2. 如果桶中没有具有相同哈希码的对象,则将对象添加到放置操作并为获取操作返回null。
  3. 如果存储桶中存在具有相同哈希码的其他对象,则“key” equals方法开始发挥作用。
    • 如果equals()返回true并且是put操作,则对象值将被覆盖。
    • 如果equals()返回false并且是put操作,则新条目将添加到存储桶中。
    • 如果equals()返回true并且是get操作,则将返回对象值。
    • 如果equals()返回false并且是get操作,则将返回null。

下图显示了HashMap的存储桶项以及它们的equals()和hashCode()之间的关系。当两个键具有相同的哈希码时出现的现象称为哈希冲突。如果hashCode()方法没有正确实现,将会有更多的哈希冲突,导致映射条目在存储桶中分布不均,从而降低了get和put操作的速度。这是在生成哈希码时使用质数的原因,以确保映射条目在所有存储桶中得到适当分布。

如果我们不同时实现 hashCode() 和 equals() 方法会发生什么呢?

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

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

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

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

Source:
https://www.digitalocean.com/community/tutorials/java-equals-hashcode