為什麼要重寫hashcode()和equals()方法
以JDK1.8原始碼詳解。
一、Object類的hashcode和equals方法
equals方法原始碼:
/** * Indicates whether some other object is "equal to" this one. * <p> * The {@code equals} method implements an equivalence relation * on non-null object references: * <ul> * <li>It is <i>reflexive</i>: for any non-null reference value * {@code x}, {@code x.equals(x)} should return * {@code true}. * <li>It is <i>symmetric</i>: for any non-null reference values * {@code x} and {@code y}, {@code x.equals(y)} * should return {@code true} if and only if * {@code y.equals(x)} returns {@code true}. * <li>It is <i>transitive</i>: for any non-null reference values * {@code x}, {@code y}, and {@code z}, if * {@code x.equals(y)} returns {@code true} and * {@code y.equals(z)} returns {@code true}, then * {@code x.equals(z)} should return {@code true}. * <li>It is <i>consistent</i>: for any non-null reference values * {@code x} and {@code y}, multiple invocations of * {@code x.equals(y)} consistently return {@code true} * or consistently return {@code false}, provided no * information used in {@code equals} comparisons on the * objects is modified. * <li>For any non-null reference value {@code x}, * {@code x.equals(null)} should return {@code false}. * </ul> * <p> * The {@code equals} method for class {@code Object} implements * the most discriminating possible equivalence relation on objects; * that is, for any non-null reference values {@code x} and * {@code y}, this method returns {@code true} if and only * if {@code x} and {@code y} refer to the same object * ({@code x == y} has the value {@code true}). * <p> * 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. * * @param obj the reference object with which to compare. * @return {@code true} if this object is the same as the obj * argument; {@code false} otherwise. * @see #hashCode() * @see java.util.HashMap */ public boolean equals(Object obj) { return (this == obj); }
該equals方法,是一個非空物件引用集合上的等價關係,滿足下面幾點:
1、反身性(reflexive)
對於任意的非空引用x,也就是說x不等於null,有x.equals(x)總是返回true。
2、對稱性(symmetric)
對於任意的非空引用x和y,有x.equals(y)返回true當且僅當y.equals(x)返回true。
3、傳遞性(transitive)
對於任意的非空引用x、y和z,如果x.equals(y)返回true且y.equals(z)返回true,那麼x.equals(z)返回true。
4、一致性(consistent)
對於任意的非空引用x和y,如果用於equals比較的物件沒有被修改的話,那麼,對此呼叫x.equals(y)要麼一致地返回true,要麼一致的返回false。
5、非空性(null)
對於任意的非空引用x,x.equals(null)總是返回false。
hashcode方法原始碼:
/** * Returns a hash code value for the object. This method is * supported for the benefit of hash tables such as those provided by * {@link java.util.HashMap}. * <p> * 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.) * * @return a hash code value for this object. * @see java.lang.Object#equals(java.lang.Object) * @see java.lang.System#identityHashCode */ public native int hashCode();
JDK1.8中hashcode的原始碼不可見。但能看到hashcode以下幾點約定:
1、在程式的一次執行過程中,對同一個物件呼叫hashcode必須一致地返回同一個整數。在同一個程式執行兩次中hashcode產生的整數不必要保持一致。
2、根據equals(Object)方法計算的兩個物件是相等的,那麼兩個物件呼叫hashcode方法會產生相同的整數。
3、根據equals(Object)方法計算的兩個物件不相等,那麼兩個物件呼叫hashcode方法也有可能產生相同的整數,如果產生的整數不同,將會改善雜湊表的效能。
注意:hashCode返回的並不一定是物件的(虛擬)記憶體地址,具體取決於執行時庫和JVM的具體實現。
二、為什麼要重寫equals方法
先看一段未重寫equals方法的程式碼:
package com.leboop;
public class EqualsTest {
public static void main(String[] args) {
Person p1 = new Person("zhangsan",23);
Person p2 = new Person("zhangsan",23);
//輸出false
System.out.println(p1.equals(p2));
}
}
package com.leboop;
public class Person {
private String name;
private Integer age;
public Person() {
}
public Person(String name, Integer age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
主程式中兩個Person例項,按照實際情況具有相同姓名和年齡(僅考慮姓名和年齡)應該是同一個人,而程式判斷不是同一個人,這是因為Java中所有的類都繼承自Object,當然自定義的Person類也不例外。所以p1.equals(p2)呼叫的是父類Object中equals方法,當然不是同一各物件。我們如下重寫equals方法:
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (age == null) {
if (other.age != null)
return false;
} else if (!age.equals(other.age))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
重寫的equals方法判斷的基本邏輯如下:
(1)如果比較的兩個物件是同一個物件(指引用地址相同),直接返回true;
(2)如果被比較的物件是null,返回false;
(3)如果兩個物件所屬的類不相同,直接返回false;
(4)當前面的條件都不滿足時,將被比較物件轉換成Person物件,然後對姓名和年齡進行比較,如果兩者均相等時,返回true,否則返回false;
所以程式將會輸出true。
三、為什麼要重寫hashcode方法
假設如前面已經重寫了equals方法,但是沒有重寫hashcode方法,看如下一段程式碼:
package com.leboop;
public class Person {
private String name;
private Integer age;
public Person() {
}
public Person(String name, Integer age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (age == null) {
if (other.age != null)
return false;
} else if (!age.equals(other.age))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
package com.leboop;
public class HashCodeTest {
public static void main(String[] args) {
Person p1 = new Person("zhangsan",23);
Person p2 = new Person("zhangsan",23);
//輸出false
System.out.println(p1.hashCode()==p2.hashCode());
}
}
程式會輸出false,認為兩個相等的物件hashcode不相等,這與hashcode方法的約定是矛盾的。所以我們如下重寫hashcode方法:
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((age == null) ? 0 : age.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
重寫執行程式將輸出true。