Java equals()和hashCode()

时间:2020-02-23 14:36:30  来源:igfitidea点击:

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

Java equals()

对象类定义了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()方法实现中使用的任何对象属性。

  • 仅当两个引用都指向同一对象时,对象类的equals()方法实现才返回" true"。

Java hashCode()

Java Object hashCode()是一种本地方法,它返回对象的整数哈希码值。
hashCode()方法的常规协定为:

  • 除非对equals()方法中使用的object属性进行修改,否则hashCode()的多次调用应返回相同的整数值。

  • 对象哈希码值可以在同一应用程序的多次执行中更改。

  • 如果根据equals()方法,两个对象相等,则它们的哈希码必须相同。

  • 如果根据equals()方法,两个对象不相等,则它们的哈希码不需要相同。
    它们的哈希码值可能相等也可能不相等。

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

Java hashCode()和equals()方法在Java中基于哈希表的实现中用于存储和检索数据。
我已经在HashMap如何在Java中工作了详细解释了。

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.theitroad.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.theitroad.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("hyman");
		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("hyman");
		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 Hash表实现对获取和放置操作使用以下逻辑。

  • 首先使用"密钥"哈希码识别要使用的"桶"。

  • 如果存储桶中不存在具有相同哈希码的对象,则添加该对象以进行放置操作,并为获取操作返回null。

  • 如果存储桶中还有其他具有相同哈希码的对象,则使用"键" equals方法。
    如果equals()返回true且是放置操作,则覆盖对象值。

  • 如果equals()返回false且是放置操作,则新条目将添加到存储桶中。

  • 如果equals()返回true并且是get操作,则返回对象值。

  • 如果equals()返回false并且是get操作,则返回null。

下图显示了HashMap的存储桶项以及它们的equals()和hashCode()是如何关联的。

两个键具有相同哈希码的现象称为哈希冲突。
如果hashCode()方法未正确实现,则哈希冲突数量将更多,并且映射条目将无法正确分布,从而导致get和put操作变慢。
这就是在生成哈希码时使用质数的原因,以便映射条目正确分布在所有存储桶中。

如果我们不同时实现hashCode()和equals()怎么办?

上面我们已经看到,如果未实现hashCode(),我们将无法检索该值,因为HashMap使用哈希码来查找存储桶以查找条目。

如果我们仅使用hashCode()而未实现equals(),那么由于equals()方法将返回false,因此也不会检索到value。

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

  • 在equals()和hashCode()方法实现中使用相同的属性,以便在更新任何属性时都不会违反其合同。

  • 最好将不可变的对象用作哈希表键,这样我们就可以缓存哈希码,而不是在每次调用时都对其进行计算。
    这就是为什么String是哈希表键的理想选择的原因,因为它是不可变的,并缓存哈希码值。

  • 实现hashCode()方法,以便发生最少数量的哈希冲突,并且条目在所有存储桶中均匀分布。