Java equals()和hashCode()
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()方法,以便发生最少数量的哈希冲突,并且条目在所有存储桶中均匀分布。