使用
當我們需要自定義HashMap這種散列數據結構(HashSet,HashMap,LinkedHashMap,LinkedHashSet)的Key時候:
需要重寫hashCode()和equals(Object o)方法。
使用方法
public class Key {
private Integer id;
public Integer getId(){
return id;
}
public Key(Integer id){
this.id = id;
}
@Override
public boolean equals(Object o) {
if(o == null || !(o instanceof Key)){
return false;
}else {
return this.getId().equals(((Key)o).getId());
}
}
@Override
public int hashCode() {
int hashCode = id.hashCode();
System.out.println(hashCode);
return hashCode;
}
}
public static void main(String[] args) {
Key k1 = new Key(1);
Key k2 = new Key(1);
HashMap<Key,String> hm= new HashMap<Key,String>();
hm.put(k1,"key with id is 1");
System.out.println(hm.get(k2));
}
我們想通過hm.get(k2)來獲取得到“key with id is 1”的打印。
爲什麼
1.HashMap爲了查詢效率,存放的時候是通過散列表來存儲的。所以需要重寫hashCode來保證存儲的數組下標一致。
2.equals方法是用來保證相等的。
通過散列來存儲,何爲散列表?
散列思想
正常我們查詢是O(n)。
例如:
在長度爲n(假設是500)的線性表(Arraylist)存放着無序的數字。
如果我們要找到一個指定的數字,就不得不通過從頭到位一次遍歷來查找,這樣的平均查找次數是n除以2(250)
散列表用的是數組支持按照下標隨機訪問數據的特性,所以散列表其實就是數組的一種擴展,由數組演化而來。可以說,如果沒有數組,就沒有散列表。
利用數組支持根據下標隨機訪問的時候,時間複雜度是O(1)。
例如我們生活中的一個場景:
學校需要點名,學校3年2班的學號25定義爲:030225,這個人是張三。
如果我們想要找到張三,我們只需大喊一聲030225,則張三就會喊一聲到!
我們很快就可以找到張三這個人。
030225的編號我們叫作鍵(key)或者關鍵字
張三我們稱作爲散列值。
030225轉化爲數組下標的映射方法就叫作散列函數。
散列函數在散列表中起着非常關鍵的作用。正常情況下散列函數的計算是比較複雜的。
我們看稍微複雜一點:
存放元素和存放位置是用hash來關聯的。hash函數是x*x%5
6這個元素會放到索引爲1的位置。7的hash函數結果爲4。
我們想要從散列表中找6這個元素。我們可以先對元素進行一次hash運算。然後再從結果中找到這個位置。
如果再放入8的話,就會產生Hash衝突。
hash衝突通過一個鏈來將7和8關聯起來。這個方法就是鏈地址法。
java中Object類會有HashCode方法。這個HashCode方法就是我們所說的散列函數。HashCode返回的值就是散列值。
我們再回頭看上面的結論:
1.HashMap爲了查詢效率,存放的時候是通過散列表來存儲的。所以需要重寫hashCode來保證存儲的數組下標一致。
就明白了。
equals方法是用來保證相等的
Object.equals方法只是比較的對象的地址。
再次看一開始的例子:
```java
public class Key {
private Integer id;
public Integer getId(){
return id;
}
public Key(Integer id){
this.id = id;
}
@Override
public boolean equals(Object o) {
if(o == null || !(o instanceof Key)){
return false;
}else {
return this.getId().equals(((Key)o).getId());
}
}
@Override
public int hashCode() {
int hashCode = id.hashCode();
return hashCode;
}
}
public static void main(String[] args) {
Key k1 = new Key(1);
Key k2 = new Key(1);
HashMap<Key,String> hm= new HashMap<Key,String>();
hm.put(k1,"key with id is 1");
System.out.println(hm.get(k2));
}
沒有重寫hahcode方法的時候,此調用是不對的調用結果是null。
原因:
當我們向hashcode裏面放k1的時候,首先會調用Key這個類的HashCode方法來計算它的Hash值,隨後把這個值放入這個值所索引的內存的位置。
沒有重新定義HashCode的方法,它就會調用Object類的Hashcode方法。而Object裏面返回的Hash值,是Key1的內存地址。
k2也是如此,但是k1和k2的內存地址是不一樣的。
如果我們用k2去拿
hm.get(k2)
自然拿不到k1的值。
如果重寫了hash值,則調用的hashCode是一樣的。但是比較兩個數是否一樣,光hashCode值一樣是不行的。並不保證k1和k2真正的相等。還需要重寫equals方法。因爲hashcode相等的時候,我們僅僅保證hashmap的數組下標相等。hash過程中會有散列衝突。真正保證相等的是equals方法。
如果不重寫equals方法,它會調用equals方法的內存地址是否是一樣。由於k1和k2是new出來的。他們的內存地址是絕對不一樣的。
如何重寫?
重寫hashcode方法時候,我們判斷定義可以就是如果兩個對象一樣,他們的id就是一樣的。
equals不爲空或者是Object纔來判斷內存地址。
@Override
public boolean equals(Object o) {
if(o == null || !(o instanceof Key)){
return false;
}else {
return this.getId().equals(((Key)o).getId());
}
}
@Override
public int hashCode() {
int hashCode = id.hashCode();
return hashCode;
}
再次總結:
我們終於知道了這個使用:
當我們需要自定義HashMap這種散列數據結構(HashSet,HashMap,LinkedHashMap,LinkedHashSet)的Key時候:
需要重寫hashCode()和equals(Object o)方法。
參考:
《Java編程思想》 17.9散列與散列碼