equals和hashCode都是Object對象中的非final方法,它們設計的目的就是被用來覆蓋(override)的,因此在程序設計中仍是常常須要處理這兩個方法的。而掌握這兩個方法的覆蓋準則以及它們的區別仍是很必要的,相關問題也很多。面試
下面咱們繼續以一次面試的問答,來考察對equals和hashCode的掌握狀況。數組
==
運算符了,爲何還須要equals啊?equals()的做用是用來判斷兩個對象是否相等,在Object裏面的定義是:性能優化
public boolean equals(Object obj) {
return (this == obj);
}
複製代碼
這說明在咱們實現本身的equals方法以前,equals等價於==
,而==
運算符是判斷兩個對象是否是同一個對象,即他們的地址是否相等。而覆寫equals更多的是追求兩個對象在邏輯上的相等,你能夠說是值相等,也可說是內容相等。bash
在如下幾種條件中,不覆寫equals就能達到目的:dom
@Override public boolean equals(Object obj) { throw new AssertionError();}
這個我在Effective Java上看過,沒記錯的話應該是:ide
自反性:對於任何非空引用值 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 和 y,屢次調用 x.equals(y) 始終返回 true 或始終返回 false, 前提是對象上 equals 比較中所用的信息沒有被修改。測試
非空性:對於任何非空引用值 x,x.equals(null) 都應返回 false。
對稱性就是x.equals(y)時,y也得equals x,不少時候,咱們本身覆寫equals時,讓本身的類能夠兼容等於一個已知類,好比下面的例子:
public final class CaseInsensitiveString {
private final String s;
public CaseInsensitiveString(String s) {
if (s == null)
throw new NullPointerException();
this.s = s;
}
@Override
public boolean equals(Object o) {
if (o instanceof CaseInsensiticeString)
return s.equalsIgnoreCase(((CaseInsensitiveString)o).s);
if (o instanceof String)
return s.equalsIgnoreCase((String) o);
return false;
}
}
複製代碼
這個想法很好,想建立一個無視大小寫的String,而且還可以兼容String做爲參數,假設咱們建立一個CaseInsensitiveString:
CaseInsensitiveString cis = new CaseInsensitiveString("Case");
複製代碼
那麼確定有cis.equals("case")
,問題來了,"case".equals(cis)
嗎?String並無兼容CaseInsensiticeString,因此String的equals也不接受CaseInsensiticeString做爲參數。
因此有個準則,通常在覆寫equals只兼容同類型的變量。
傳遞性就是A等於B,B等於C,那麼A也應該等於C。
假設咱們定義一個類Cat。
public class Cat()
{
private int height;
private int weight;
public Cat(int h, int w)
{
this.height = h;
this.weight = w;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Cat))
return false;
Cat c = (Cat) o;
return c.height == height && c.weight == weight;
}
}
複製代碼
名人有言,無論黑貓白貓抓住老鼠就是好貓,咱們又定義一個類ColorCat:
public class ColorCat extends()
{
private String color;
public ColorCat(int h, int w, String color)
{
super(h, w);
this.color = color;
}
複製代碼
咱們在實現equals方法時,能夠加上顏色比較,可是加上顏色就不兼容和普通貓做對比了,這裏咱們忘記上面要求只兼容同類型變量的建議,定義一個兼容普通貓的equals方法,在「混合比較」時忽略顏色。
@Override
public boolean equals(Object o) {
if (! (o instanceof Cat))
return false; //不是Cat或者ColorCat,直接false
if (! (o instanceof ColorCat))
return o.equals(this);//不是彩貓,那必定是普通貓,忽略顏色對比
return super.equals(o)&&((ColorCat)o).color.equals(color); //這時候才比較顏色
}
複製代碼
假設咱們定義了貓:
ColorCat whiteCat = new ColorCat(1,2,"white");
Cat cat = new Cat(1,2);
ColorCat blackCat = new ColorCat(1,2,"black");
複製代碼
此時有whiteCat等於cat,cat等於blackCat,可是whiteCat不等於blackCat,因此不知足傳遞性要求。。
因此在覆寫equals時,必定要遵照上述的5大軍規,否則老是有麻煩事找上門來。
手寫:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
複製代碼
上面的equals有如下幾點訣竅:
==
比較,float域用Float.compare方法,double域用Double.compare方法,至於別的引用域,咱們通常遞歸調用它們的equals方法比較,加上判空檢查和對自身引用的檢查,通常會寫成這樣:(field == o.field || (field != null && field.equals(o.field)))
,而上面的String裏使用的是數組,因此只要把數組中的每一位拿出來比較就能夠了。還有一些注意點。
hashCode用於返回對象的hash值,主要用於查找的快捷性,由於hashCode也是在Object對象中就有的,因此全部Java對象都有hashCode,在HashTable和HashMap這一類的散列結構中,都是經過hashCode來查找在散列表中的位置的。
以HashMap爲例,使用的是鏈地址法來處理散列,假設有一個長度爲8的散列表
0 1 2 3 4 5 6 7
複製代碼
那麼,當往裏面插數據時,是以hashCode做爲key插入的,通常hashCode%8獲得所在的索引,若是所在索引處有元素了,則使用一個鏈表,把多的元素不斷連接到該位置,這邊也就是大概提一下HashMap原理。因此hashCode的做用就是找到索引的位置,而後再用equals去比較元素是否是相等,形象一點就是先找到桶(bucket),而後再在裏面找東西。
一個好的hashCode的方法的目標:爲不相等的對象產生不相等的散列碼,一樣的,相等的對象必須擁有相等的散列碼。
好的散列函數要把實例均勻的分佈到全部散列值上,結合前人的經驗能夠採起如下方法:
引自Effective Java
把某個非零的常數值,好比17,保存在一個int型的result中;
對於每一個關鍵域f(equals方法中設計到的每一個域),做如下操做:
a. 爲該域計算int類型的散列碼;
i.若是該域是boolean類型,則計算(f?1:0),
ii.若是該域是byte,char,short或者int類型,計算(int)f,
iii.若是是long類型,計算(int)(f^(f>>>32)).
iv.若是是float類型,計算Float.floatToIntBits(f).
v.若是是double類型,計算Double.doubleToLongBits(f),而後再計算long型的hash值
vi.若是是對象引用,則遞歸的調用域的hashCode,若是是更復雜的比較,則須要爲這個域計算一個範式,而後針對範式調用hashCode,若是爲null,返回0
vii. 若是是一個數組,則把每個元素當成一個單獨的域來處理。
複製代碼
b.result = 31 * result + c;
返回result
編寫單元測試驗證有沒有實現全部相等的實例都有相等的散列碼。
這裏再說下2.b中爲何採用31*result + c
,乘法使hash值依賴於域的順序,若是沒有乘法那麼全部順序不一樣的字符串String對象都會有同樣的hash值,而31是一個奇素數,若是是偶數,而且乘法溢出的話,信息會丟失,31有個很好的特性是31*i ==(i<<5)-i
,即2的5次方減1,虛擬機會優化乘法操做爲移位操做的。