在Java语言中,equals()
和hashCode()
是Object类中的方法,这样,每一个类中都会继承Object类中这两个方法的默认实现。
1、说明
1.1 equals()
方法
这个方法在Object类中的定义如下:
public boolean equals(Object obj) {
return (this == obj);
}
如果要自己覆盖实现在这个方法,需要遵守如下原则:
- For any object x,
x.equals(x)
should returntrue
.- For any two object x and y,
x.equals(y)
should returntrue
if and only ify.equals(x)
returnstrue
.- For multiple objects x, y, and z, if
x.equals(y)
returnstrue
andy.equals(z)
returnstrue
, thenx.equals(z)
should returntrue
.- Multiple invocations of
x.equals(y)
should return same result, unless any of the object properties is modified that is being used in theequals()
method implementation.- Object class equals() method implementation returns
true
only when both the references are pointing to same object.
1.2 hashCode()
方法
这个方法是Object类的一个原生方法,它返回一个整数,代表这个对象的Hash值。
如果要自定义覆盖实现这个方法,需要注意遵守:
- Multiple invocations of hashCode() should return the same integer value, unless the object property is modified that is being used in the equals() method.
- An object hash code value can change in multiple executions of the same application.
- If two objects are equal according to equals() method, then their hash code must be same.
- If two objects are unequal according to equals() method, their hash code are not required to be different. Their hash code value may or may-not be equal.
1.3 equals()
和hashCode()
使用约定
Java中基于Hash的数据结构在存储和检索数据的时候,会用到这两个方法。他们的实现,需要遵守:
- If
o1.equals(o2)
, theno1.hashCode() == o2.hashCode()
should always betrue
.- If
o1.hashCode() == o2.hashCode
is true, it doesn’t mean thato1.equals(o2)
will betrue
.
2、什么时候需要重写这两个方法
当重写equals()方法的时候,为了不违反前面的约定,基本一定要重写hashCode()方法。需要注意的是,即使违反了前面的约定,程序也不会抛出异常。如果这个类的对象没有用来作为Hash表的key值的话,没有问题。但是,如果这个类的对象被用来做为Hash表的key值,那么,必须要重新覆盖实现equals()和hashCode()方法,不然,如果依赖于默认的实现,会有问题。如下:
com/lfqy/trying/hashcodeequals/DataKey.java
:
package com.lfqy.trying.hashcodeequals;
/**
* Created by chengxia on 2022/8/25. */public class DataKey {
private String name;
private int id;
// getter and setter methods
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
return "DataKey [name=" + name + ", id=" + id + "]";
}
}
com/lfqy/trying/hashcodeequals/HashingTest.java
:
package com.lfqy.trying.hashcodeequals;
import java.util.HashMap;
import java.util.Map;
/**
* Created by chengxia on 2022/8/25. */public class HashingTest {
public static void main(String[] args) {
Map<DataKey, Integer> hm = getAllData();
DataKey dk = new DataKey();
dk.setId(1);
dk.setName("Paopao");
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("Paopao");
System.out.println(dk.hashCode());
hm.put(dk, 10);
return hm;
}
}
运行后,输出如下:
1627674070
1360875712
null
Process finished with exit code 0
这里之所以最后取到的值是null,原因在于:在取值的时候,value = hm.get(dk)
会先调用dk.hashCode()
计算这个对象的HashCode,判断要取的值出现在哪个Hash桶,然后,调用equals方法,逐个检查该桶中的对象是否有返回true的,如果有就将这个对象返回,如果没有就返回null。从这个过程看,这里两个对象的hashCode值不一样,也不是一个对象,自然没法找到对应key的值,因此就返回null。
3、如何自定义覆盖实现equals()
和hashCode()
方法
一般来说,IDE都支持自动生成这两个方法。需要注意的是,为了遵守前面的约定,这里在实现这两个方法时,一定让相同的对象属性参与计算。如下是一个IDEA自动生成的例子。
com/lfqy/trying/hashcodeequals/DataKey.java
:
package com.lfqy.trying.hashcodeequals;
/**
* Created by chengxia on 2022/8/25. */public class DataKey {
private String name;
private int id;
// getter and setter methods
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
return "DataKey [name=" + name + ", id=" + id + "]";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof DataKey)) return false;
DataKey dataKey = (DataKey) o;
if (id != dataKey.id) return false;
return name != null ? name.equals(dataKey.name) : dataKey.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + id;
return result;
}
}
这样,再次运行,输出如下:
868822177
868822177
10
Process finished with exit code 0
4、Hash碰撞
Java语言中,Hash表结构的get和put函数,逻辑如下:
- First identify the “Bucket” to use using the “key” hash code.
- If there are no objects present in the bucket with same hash code, then add the object for put operation and return null for get operation.
- If there are other objects in the bucket with same hash code, then “key” equals method comes into play.
- If equals() return true and it’s a put operation, then object value is overridden.
- If equals() return false and it’s a put operation, then new entry is added to the bucket.
- If equals() return true and it’s a get operation, then object value is returned.
- If equals() return false and it’s a get operation, then null is returned.
两个对象有相同hashCode的现象,称为hash碰撞。如果hashCode函数实现有问题,就会出现较高概率的hash碰撞,进而导致键值分布不均,导致get和put操作出现效率问题。这就是为什么在生成hashCode使用了质数,主要就是为了让所有桶之间的分布比较均匀。
5、equals()
和hashCode()
的最佳实践
- Use same properties in both equals() and hashCode() method implementations, so that their contract doesn’t violate when any properties is updated.
- It’s better to use immutable objects as Hash table key so that we can cache the hash code rather than calculating it on every call. That’s why String is a good candidate for Hash table key because it’s immutable and cache the hash code value.(字符串类型是常用的key值首选。)
- Implement hashCode() method so that least number of hash collision occurs and entries are evenly distributed across all the buckets.
网友评论