为什么重写equlas方法必须要重写hashcode方法
hash,hashcode方法是干啥的
Java
中的hash
值主要是用来在散列存储结构中确定对象的存储地址的,提高对象的查询效率。
hashcode方法就是用来高效判断对象是否相等的,一般用在equals前面,hash值相同即判断两对象相等,但是由于不同对象可能拥有相同的hash(hash冲突),因此需要equals再来补充对比
equals和hashcode源码:
public class Object {
/*
* Note that it is generally necessary to override the {@code hashCode}
* method whenever this method is overridden, so as to maintain the
* general contract for the {@code hashCode} method, which states
* that equal objects must have equal hash codes.
*/
public boolean equals(Object obj) {
return (this == obj);
}
/*
* The general contract of {@code hashCode} is:
* <ul>
* <li>Whenever it is invoked on the same object more than once during
* an execution of a Java application, the {@code hashCode} method
* must consistently return the same integer, provided no information
* used in {@code equals} comparisons on the object is modified.
* This integer need not remain consistent from one execution of an
* application to another execution of the same application.
* <li>If two objects are equal according to the {@code equals(Object)}
* method, then calling the {@code hashCode} method on each of
* the two objects must produce the same integer result.
* <li>It is <em>not</em> required that if two objects are unequal
* according to the {@link java.lang.Object#equals(java.lang.Object)}
* method, then calling the {@code hashCode} method on each of the
* two objects must produce distinct integer results. However, the
* programmer should be aware that producing distinct integer results
* for unequal objects may improve the performance of hash tables.
* </ul>
* <p>
* As much as is reasonably practical, the hashCode method defined by
* class {@code Object} does return distinct integers for distinct
* objects. (This is typically implemented by converting the internal
* address of the object into an integer, but this implementation
* technique is not required by the
* Java™ programming language.)
*
*/
public int hashCode() {
return identityHashCode(this);
}
/* package-private */ static int identityHashCode(Object obj) {
int lockWord = obj.shadow$_monitor_;
final int lockWordStateMask = 0xC0000000; // Top 2 bits.
final int lockWordStateHash = 0x80000000; // Top 2 bits are value 2 (kStateHash).
final int lockWordHashMask = 0x0FFFFFFF; // Low 28 bits.
if ((lockWord & lockWordStateMask) == lockWordStateHash) {
return lockWord & lockWordHashMask;
}
return identityHashCodeNative(obj);
}
}
hashcode和equals是配套使用的
hashcode速度快,适合查询,但可能由于hash冲突,即不同对象拥有相同的hash,导致判断失败,因此,还要配合equals进一步判断,总结一下
equals
相等的两个对象,它们的hashCode
肯定相等,也就是用equals
对比是绝对可靠的;hashCode
相等的两个对象,它们的equals
不一定相等,也就是hashCode
不是绝对可靠的;
equals有约定的重写原则
equals的重写原则遵循,equals相等,则hashcode必然相等
如果只重写equals,任何使用hash都无法正常工作
以hashmap的key的唯一性举例,为什么需要同时重写equals和hashcode
众所周知,hashmap的key是唯一的,我们造一个employee的实体类,将employee对象当作key存入hashmap,代码如下:
Employee.java
public class Employee {
private String name;
private Integer age;
public Employee(String name, Integer age) {
this.name = name;
this.age = age;
}
}
test.java
public static void main(String[] args) {
Employee employee1 = new Employee("冰峰", 20);
Employee employee2 = new Employee("冰峰", 22);
Employee employee3 = new Employee("冰峰", 20);
HashMap<Employee, Object> map = new HashMap<>();
map.put(employee1, "1");
map.put(employee2, "1");
map.put(employee3, "1");
System.out.println("equals:" + employee1.equals(employee3));
System.out.println("hashCode:" + (employee1.hashCode() == employee3.hashCode()));
System.out.println(JSONObject.toJSONString(map));
}
employee1
和employee3
是相同的,运行结果如下:
equals:false
hashCode:false
{com.acerola.collection.Employee@74a14482=1, com.acerola.collection.Employee@4554617c=1, com.acerola.collection.Employee@1b6d3586=1}
employee1和3都存入了,而且默认的hashcode和equals都是false,很显然,这不满足key的唯一性,我们看一下map.put执行的hashcode源码
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
hashcode
public native int hashCode();
默认的hashcode底层使用c语言编写,这不重要,重要的是不同对象的hash值一般不同,这就让两个对象都加入了
显然hashmap如果要满足我们需求,必须重写equals和hashcode
重写代码如下:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return Objects.equals(name, employee.name) &&
Objects.equals(age, employee.age);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
再次运行
equals:true
hashCode:true
{com.acerola.collection.Employee@13dea95=1, com.acerola.collection.Employee@13dea97=1}
这次成功去重,非常漂亮
那如果我不重写hashcode呢
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return Objects.equals(name, employee.name) &&
Objects.equals(age, employee.age);
}
//@Override
//public int hashCode() {
// return Objects.hash(name, age);
//}
运行结果:
equals:true
hashCode:false
{com.acerola.collection.Employee@74a14482=1, com.acerola.collection.Employee@4554617c=1, com.acerola.collection.Employee@1b6d3586=1}
可以见得,如果只重写equals,就会出现equals为tue但是hashcode为false的矛盾情况
总结
所以,我们的结论是
-
equals与hashcode是配套使用的,hashcode的意义是提高查询速度,equlas的意义是弥补hashcode的不可靠性
-
重写原则是
equals
相等的两个对象,它们的hashCode
肯定相等 -
单单重写equals,不重写hashcode将引起与散列集合(HashMap、HashSet、HashTable、ConcurrentHashMap)的冲突