1. 程式人生 > >Java中的equals()和hashCode()

Java中的equals()和hashCode()

sea 接口 後來 ide itl 一個數 毫無 exceptio title

概述

在我們使用類集框架(比方使用hashMap、hashSet)的時候,常常會涉及到重寫equals()和hashCode()這兩個方法。

這兩個方法的聯系是:
1. 假設兩個對象不同,那麽他們的hashCode肯定不相等;
2. 假設兩個對象的hashCode同樣。那麽他們也未必相等。

所以說。假設想在hashMap裏面讓兩個不相等的對象相應同一個值,首先須要讓他們的hashCode同樣。其次還要讓他們的equals()方法返回true,因此為了達到這個目的,我們就僅僅能重寫hashCode()和equals()這兩個方法了。

引用一篇文章的講解:The idea behind a Map is to be able to find an object faster than a linear search. Using hashed keys to locate objects is a two-step process. Internally the Map stores objects as an array of arrays. The index for the first array is the hashcode() value of the key. This locates the second array which is searched linearly by using equals() to determine if the object is found.

大致意思就是:使用Map比線性搜索要快。Map存儲對象是使用數組的數組(能夠理解為二維數組。這並不準確,只是能夠依照這個理解。之前hashMap用的是數組。每一個數組節點相應一個鏈表。如今Java 8已經把鏈表改成了treeMap。相當於一個二叉樹。這樣檢索的時候比鏈表更快,尤其是在最壞情況下,由原來鏈表的O(n)變成了二叉樹的O(logn),詳見https://dzone.com/articles/hashmap-performance)。所以搜索大致是分為兩步的,第一步是依據hashCode尋找第一維數組的下標,然後依據equals的返回值推斷對象是第二維數組中的哪一個。

例證

舉個栗子——

import java.util.HashMap;

public class Apple {
    private String color;

    public Apple(String color) {
        this.color = color;
    }

    public static void main(String[] args) {
        Apple a1 = new Apple("green");
        Apple a2 = new Apple("red");

        //hashMap stores apple type and its quantity
HashMap<Apple, Integer> m = new HashMap<Apple, Integer>(); m.put(a1, 10); m.put(a2, 20); System.out.println(m.get(new Apple("green"))); } }

程序的執行結果為:null
此時,我們已經向hashMap裏面存儲兩個對象了,且a1就是green Apple,那麽為什麽我們通過”green”去查找卻返回null呢?
顯然。後來我們新new出來一個對象,這和之前加入的a1綠蘋果那個對象絕對不是同一個對象,依據終極父類Object中的hashCode()的計算結果,其返回值絕對是不一樣的。

所以——

第一步:重寫hashCode()

我們須要先讓“凡是color屬性同樣的對象,其hashCode都一樣”,所以我們能夠這樣重寫hashCode():

public int hashCode(){
    return this.color.length();
}

這裏我們使用color屬性的內容的長度作為hashCode的大小,那麽凡是green的蘋果,hashCode肯定都是5(green字符串長度為5),這樣一來,屬性同樣的對象的hashCode肯定都同樣了。這僅僅是保證了一維數組的下標找到了(姑且這樣理解),還須要找第二維的下標呢,這個須要在第二步中解決。在解決第二步之前,你可能會有問題——這樣一來的話。假設有black蘋果(假設有black),那麽它的hashCode也變成了5了啊,和green一樣了。這個同樣靠第二步解決。

第二步:重寫equals()

我們已經知道。假設想讓兩個對象一樣,除了讓他們的hashCode值一樣外,還要讓他們的equals()函數返回true,兩個都符合才算一樣。

所以第二步我們要重寫equals()函數,使“僅僅要color一樣,兩個蘋果就是同樣的”:

public boolean equals(Object obj) {
    if (!(obj instanceof Apple))    //首先類型要同樣才有可能返回true
        return false;   
    if (obj == this)    //假設是自己跟自己比。顯然是同一個對象,返回true
        return true;
    return this.color.equals(((Apple) obj).color);    //假設顏色同樣,也返回true
}

這樣一來,依據最後一句話。凡是顏色同樣的蘋果。第二維也映射到同一個位置了(姑且這麽理解)。這樣一來,就能夠依據顏色在hashMap裏尋找蘋果了。
把我們重寫過的hashCode()和equals()加入到之前的代碼中,便會輸出結果:10,即鍵a1所相應的值。

結語

感覺這篇文章涉及的內容還是相當基礎和重要的。

文章到此也差點兒相同能夠結束了。另附上曾經學習時記的筆記,感覺還是挺實用的,我自己的筆記自己看起來自然是毫無障礙的,只是實在不想整理了,就直接貼上來吧,大家將就將就看看吧,能夠作為上面內容的嘮叨和補充。

附錄1

HashSet在存儲的時候(比方存的是字符串)。則存進去之後依照哈希值排序(也就意味著遍歷的時候得到的順序不是我們加入的順序,即亂序),假設第二個對象和第一個Hash值一樣可是對象不一樣,則第二個會鏈在第一個後面。在加入對象的時候,add()返回boolean型,假設加入的對象同樣(比方兩個同樣的字符串),則返回false。加入失敗。

HashSet怎樣保證元素的唯一性?
通過元素的方法——hashCode()和equals()來實現。
假設兩個元素的hashCode不同,直接就存了;
假設兩個元素的hashCode同樣,則調用equals推斷是否為true,true則證明是同一個對象,就不存了,false的話證明是不同的對象,存之。

一旦自己定義了對象。想要存進HashSet,則一定要覆寫hashCode()和equals()方法——
比方我們定義Person類,僅含有name,age兩個參數。規定:僅僅要姓名和年齡同樣,就斷定為“同一個人”,則不能存入HashSet,否則的話能夠。
對於這種情況。假設我們new出來幾個人,當中存在名字和年齡同樣的,則均會存入HashSet。原因就是這些對象是不同的對象,所占內存不一樣,則通過hashCode()返回的哈希值也都不一樣。所以理所當然的存入了HashSet。為了避免把“同一個人”存進HashSet,我們首先須要讓hashCode()針對“同一個人”返回同樣的哈希值,即覆寫hashCode()方法!

public int hashCode(){
    return 110;
}

這樣自然也能夠,只是沒有必要讓全部的對象返回的哈希值都一樣,僅僅要“同一個人”的哈希值一樣即可了,所以寫成這樣更好:

public int hashCode(){
    return name.hashCode() + age;
}

這種話“同一個人”返回的哈希值就是同樣的。只是這樣還是不夠完美。由於覆寫的這個hashCode()盡管會讓“同樣的人”返回同樣的哈希值,但也可能會讓“不同的人”返回同樣的哈希值。比方兩個人name不同,age不同。但name的哈希值加上age恰恰同樣,這種話就坑爹了。為了避免這種現象,讓這種哈希值恰巧撞上的概率進一步減小,我們寫成這樣會更好:

public int hashCode(){
    return name.hashCode() + age * 19;
}

最好是乘上一個素數之類的,能夠大大減少“不同的人”的哈希值撞上的概率。


通過以上hashCode()函數的覆寫。我們讓“同樣的人”的哈希值同樣了,那麽接下來就要覆寫equals()函數。由於Java碰到哈希值同樣的情況之後,接下來要依據equals()函數推斷兩個對象是否同樣,同樣則不再存入HashSet,不同的話就存進去,且是鏈在和它哈希值同樣的對象上的(鏈成一串兒)。

附錄2:下面是TreeSet內容。和上面關系不大了

TreeSet能夠對裏面的元素進行排序,比方假設對象是字符串,則依照字典序排序。

假設要存自己定義對象,須要讓自己定義的對象具有比較性。這種話TreeSet才幹將其依照一定的順序去排序。

否則會報出異常。為了讓自己定義對象能夠具有比較性。對象須要實現Comparable接口。
比方,有一個Person類。我們要new出來一些人放到TreeSet裏面,為了使對象具有可比性從而能夠存入TreeSet。我們規定依照對象的年齡排序。
首先,讓Person類實現Comparable接口,覆寫接口的compareTo()方法。其返回值和c語言的strcmp()同樣:

這種話。假設某兩個對象的年齡同樣,則後來者將不會被存入TreeSet,由於後來者被覺得和之前的那個是同一個對象。
所以我們對主關鍵字排序之後一定要對次關鍵字進行排序。僅僅有全部的關鍵字都比較完成還是返回0,我們才幹覺得兩個對象同樣。


所以覆寫的compareTo()應該是這種:

public int compareTo(Object obj){   //傳進來的須要是Object類型,這一點要註意
    if(!(obj instanceof Person)){   //傳進來的對象不正確,直接拋出異常
        throw new RuntimeException("Not The Same Kind Of Object!");
    }

    Person p = (Person)obj;         //將對象向下轉型

    if(this.age < p.age)
        return -1;
    if(this.age > p.age)
        return 1;
    if(this.age == p.age)
    {
        return this.name.compareTo(p.name);     //String類中覆寫過Comparable接口的空方法compareTo()。依照字典序對字符串進行排序
    }
}

因此,假設你想讓TreeSet依照輸入順序存數據,而不是自己主動排序。能夠這樣覆寫compareTo()方法:

public int compareTo(Object obj){
    return 1;
}

非常easy,只是缺陷就是“同樣的人”也會被存進去。這個函數是全然依照輸入內容的順序不加以不論什麽刪改原模原樣原順序存進TreeSet的。
同理,假設return -1就是輸入順序的逆序;假設return 0則僅僅能存入第一個輸入的對象。

以上是TreeSet排序的第一種方法——讓元素自身具有比較性(讓類實現Comparable接口,覆寫compareTo()方法)。


只是這個方案有缺陷,比方我突然不想依照年齡排,想依照姓名排序。這就須要又一次改動代碼了。

所以TreeSet排序的另外一種方法——讓集合自身具有比較性(將自己定義的比較器傳入Treeset的構造函數)。


比方我們如今要依照人名字典序排序:
首先建造一個比較器。實現Comparator接口,覆寫compare()方法(返回值也和strcmp()一樣):

public PersonCompareMethod implements Comparator{
    public int compare(Object obj1, Object obj2){       //傳入兩個Object對象
        Person p1 = (Person)obj1;                       //向下轉型
        Person p2 = (Person)obj2;

        int num = p1.getName().compareTo(p2.getName()); //直接比較兩個字符串的字典序,由於String類已經覆寫過Comparable接口的空方法compareTo()。依照字典序對字符串進行排序
        if(num == 0){
            return new Integer(p1.getAge()).compareTo(new Integer(p2.getAge()));
                    //這一句話比較巧妙!!!本來我們是能夠直接依照數字大小比較年齡這個次要關鍵字的,只是在比較的時候,我們換了一種比較高端的方法:新建了兩個Integer對象,由於Integer類裏面也有compareTo()方法。將數字依照字典序進行比較。

當然是實現了Comparable接口並覆寫compareTo()方法才具有這種功能 } return num; } }

之後,將我們的構造器傳入TreeSet新建對象時調用的構造函數即可:
TreeSet ts = new TreeSet(new MyCompareMethod());

使用另外一種方式的情況:
對象不具有比較性。或者是比較性並非我們所須要的。

Java中的equals()和hashCode()