對於這兩個方法的研究,源於一道比較經典的面試題:「x.equals(y)==true;x,y可有不一樣的hashcode對嗎?」,其實這道題的關鍵在於考咱們對equals()方法和hashCode()方法的理解,網上看了很多文章,有說對的,也有說不對的。在我看來對也不對,具體緣由,咱們下面慢慢分析。java
equals()方法是Object中定義的方法,任何類均可以重寫,可是須要遵循必定的規範,咱們看一下Object中的默認實現面試
public boolean equals(Object obj) {
return (this == obj);
}複製代碼
能夠看到就是對兩個對象的比較,也就是兩個對象的地址值是否相等。然而有的時候,默認的比較方式並不能知足咱們的需求,好比要咱們判斷兩個學生對象是不是同一個,須要從學號、姓名等方面來比較,這時候就要重寫equals()方法了,這個重寫規則,JAVA是有明確的規範的:bash
2.1 自反性:x.equals(x)必須爲true;
2.2 對稱性:x.equals(y)和y.equals(x)返回值必須相等;
2.3 傳遞性:x.equals(y)爲true,和y.equals(z)爲true,那麼x.equals(z)也必須爲true;
2.4 一致性:若是對象x和y在equals()中使用的信息沒有改變,那麼x.equals(y)的值始終不變;
2.5 非null : x不是null,y是null,那麼x.equals(y)必須爲false。複製代碼
這是Android SDK中Object類的hashCode()方法實現:ide
/**
* 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>
*/
public int hashCode() {
int lockWord = 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 System.identityHashCode(this);
}
public static native int identityHashCode(Object x);複製代碼
經過註釋咱們能夠得知一個很重要的信息,那就是hashCode()這個方法主要是爲了更好的支持哈希表(如HashMap、HashSet、HashTable等)。說到這,咱們有必要了解一下哈希表的存儲原理了:工具
當咱們向哈希表(如HashMap、HashSet、HashTable等)插入一個object時,首先調用hashcode()方法得到該對象的哈希碼,經過該哈希碼能夠直接定位object在哈希表中的位置(通常是哈希碼對哈希表大小取餘),若是該位置沒有對象,那麼直接將object插入到該位置;若是該位置有對象(可能有多個,經過鏈表實現),則調用equals()方法將這些對象與object比較,若是相等,則不須要保存object,不然,將該對象插入到該鏈表中。性能
這也就解釋了,爲何equals()相等,那麼hashCode()必須相等。由於,若是兩個對象的equals()方法返回true,則它們在哈希表中只應該出現一次;若是hashCode()不相等,那麼它們會被散列到表中不一樣的位置,哈希表中不止出現一次。測試
JAVA建議,若是咱們重寫了equals()方法,那麼也要重寫hashCode()方法,由於默認的hashCode()方法返回的是該對象內存中的地址。仍是上面的例子,咱們進行兩個學生的比較,依據是學號和姓名,若是都相等,那麼認爲是同一個對象,有以下代碼:ui
public class Student {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Main {
public static void main(String[] args) {
Student student1 = new Student();
student1.setId(1);
student1.setName("name1");
Student student2 = new Student();
student2.setId(1);
student2.setName("name1");
System.out.println(student1.equals(student2));
}
}複製代碼
這時候返回的是false,確定不是咱們想要的,因此咱們須要重寫equals()方法,讓它符合咱們的業務需求,重寫後的代碼以下:this
public class Student {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
if (getId() != student.getId()) return false;
return getName() != null ? getName().equals(student.getName()) : student.getName() == null;
}
public class Main {
public static void main(String[] args) {
Student student1 = new Student();
student1.setId(1);
student1.setName("name1");
Student student2 = new Student();
student2.setId(1);
student2.setName("name1");
System.out.println(student1.equals(student2));
}
}
}複製代碼
經過重寫equals()方法以後,打印結果變成了true,可是這樣就完了嗎?咱們進行另一種測試:google
public class Main {
public static void main(String[] args) {
Student student1 = new Student();
student1.setId(1);
student1.setName("name1");
Student student2 = new Student();
student2.setId(1);
student2.setName("name1");
System.out.println(student1.equals(student2));
Set<Student> set = new HashSet<>();
set.add(student1);
set.add(student2);
System.out.println("setSize: " + set.size());
}
}複製代碼
上面student1.equals(student2)返回的是true,setSize理應返回的結果是1纔對,可是返回的確是2,這是由於它們的hashCode是不同的,因此Set集合認爲它們是兩個不一樣的對象,所以添加了兩次。這時候hashCode()就排上用場了,咱們須要重寫hashCode()方法,這就是爲何JDK說,若是重寫了equals()方法,必需要重寫hashCode()方法的緣由,:
public class Student {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
if (getId() != student.getId()) return false;
return getName() != null ? getName().equals(student.getName()) : student.getName() == null;
}
@Override
public int hashCode() {
int result = id;
result = 31 * result + name.hashCode();
return result;
}
}複製代碼
這樣纔算完事了。
hashCode()方法重寫的一些原則:
a.若是重寫equals()方法,檢查條件「兩個對象經過equals()方法判斷相等,那麼它們的hashCode()也應該相等」是否成立,若是不成立,則重寫hashCode()方法。
b.hashCode()不能太簡單,不然容易形成hash衝突;
c.hashCode()不能太複雜,不然會影響性能。複製代碼
可是通常來講咱們不須要本身去寫,這裏有幾種便捷的實現方式:
(1) Google的Guava項目裏有處理hashCode()和equals()的工具類com.google.common.base.Objects;
(2) Apache Commons也有相似的工具類EqualsBuilder和HashCodeBuilder;
(3) Java 7 也提供了工具類java.util.Objects;
(4) 經常使用IDE都提供hashCode()和equals()的代碼生成。複製代碼
至此,咱們已經能夠解答最開始的問題:「x.equals(y)==true;x,y可有不一樣的hashcode對嗎?」
嚴格來講,若是兩個對象x.equals(y)==true,那麼它們的hashCode()也要相等,若是不相等,當把它們添加到哈希表中時就會出現數據混亂(如重複添加);可是,因爲hashCode()方法並非強制實現的,因此,是可能存在不一樣的hashcode的。
若是想寫出優秀的代碼又不想踩坑就遵循官方的規範吧。