判斷兩個對象是否等價,是OOP編程中常見的需求(下面圍繞Java來進行闡述)。編程
考慮這樣幾種狀況:經過某個特徵值來判斷兩個對象是否「等價」,當這兩個對象等價時,判斷結果爲true,不然結果爲false。ide
固然,這裏的「特徵值」不會只是簡單的「對象引用」,事實上,Object類(Java的「對象世界」的根)中實現的equals方法,就是把「特徵值」設定爲「對象引用」來進行判斷等價性的,所以能夠得知,Object類中equals方法只是簡簡單單地返回this引用和被判斷的obj的引用的「==運算」的值。測試
可是不少狀況下,並非要求兩個對象只有引用相同時(此時兩者爲一個對象)才「斷定爲等價」,這就須要ADT設計者來界定兩個實例對象判斷等價的條件,即設定要比較的特徵值。this
舉個例子,在某個社交軟件中,要求每一個用戶的用戶名(name)必須獨一無二,那麼在每次增長新用戶的時候,都要對該用戶的註冊名進行判斷,若是當前用戶名已經被佔用,則沒法爲該用戶建立帳號,只能要求該新用戶從新選擇設定用戶名。spa
這個時候,能夠肯定,判斷兩個對象是否等價的特徵值就是name(不妨把它設定爲一個String類型的私有屬性),這裏對這個實現過程進行模擬。設計
這裏說明一下equals內代碼的我的寫法:對象
第一步,先判斷引用值是否相等,此時person1.equals(person1)這樣的狀況,就能夠很快返回結果true。blog
第二步,判斷類型是否匹配,若是兩個對象等價,前提是它們必定爲相同的類型,此時person1.equals(null)這樣的狀況,也能進行判斷並返回結果false。排序
第三步,循序漸進地按照預設的特徵值進行對象的等價性判斷。開發
運行結果:
這裏說明幾點:
1.類中的equals方法是必定要重寫/覆蓋(Override)的,由於要讓它按照設計的需求來根據特徵值判斷等價性。
這裏的特徵值,就是String類型的name屬性,表示每一個Person對象的名字。因爲在equals方法中只設定了這一個須要比較的特徵值,所以只要兩個Person類對象的name相同,那麼他們的判斷結果就是相同。
2.類中的hashCode方法須要重寫/覆蓋
事實上,當實現了1以後,就能保證判斷兩個對象等價性是否成立了(此時已經能保證程序中person1.equals(person2)值爲true。可是這樣獲得的equals方法是有很大限定性的。好比把person1加入到一個HashSet中,此時判斷HashSet中是否包含person2,因爲在設計時,特徵值只是name,那麼此時指望HashSet.contains(person2)的值也應爲true,但若是不實現hashCode方法,返回值只能是false。
對於這個緣由,能夠把Java中每一個實例對象的存儲過程都想象成「將包含該對象的數據‘拋到’一個桶裏」,爲了更快地比價,就把整個程序運行時的空間,分紅至關多的「桶」,併爲每一個桶編號,對於桶內裝載的數據,有這樣的規定:爲每一個實例對象進行編號,只有編號相同的兩個對象,它們纔有可能分配到一個桶裏。這樣一來,要想判斷兩個對象是否等價(便是否能讓equals方法返回true),只須要訪問這個桶就能夠了,由於這兩個對象必定是出如今相同的桶裏的。步驟1已經實現了「找到兩個對象以後,根據某個特徵值進行判斷」,可是並未實現「讓兩個對象分配到一個桶裏」。這就是問題的關鍵所在。因此爲了保證兩個對象分配到相同的「桶」裏,就要重寫它們的hashCode方法,Java中爲每種類型都默認實現了該類型的hashCode方法。下面的實現了hashCode的代碼中,因爲特徵值是name,爲了保證這兩個Person類對象等價,那麼它們的name必定相同,那考慮到name(Sting類型)已經實現了hashCode,此時就簡單地把它們的name的hashCode值進行返回便可。這樣就能保證,若是兩個Person對象的name若是相同,那麼它們的hashCode必定相同,同時也便於下一步判斷。
注:重點在於理解這個「桶」的概念,經過這個抽象過程,便也能夠很好地理解「Java中兩個等價的對象必定有相同的hashCode值,但兩個擁有相同hashCode值的對象不必定等價」這句話。這句話的重點就在於考慮「桶」是如何裝載的、以及它「裝載」的是什麼類型對象等等細節。
這裏給出未實現hashCode的Person類,並展現其測試代碼:
測試結果:
可見,未實現hashCode時,set.contains(person2)爲false,即此時HashSet類型在檢索person2時,發現它不在其裝載對象(perosn1)所在的「桶」裏,因而直接返回false。
此時,從新實現代碼:
此時再次測試上述測試代碼,測試結果:
能夠看到,儘管測試set未裝載person2,但根據重寫的equals斷定等價性規則,person2也是被斷定符合等價性的,所以在實現了hashCode後,便也能讓持有對象按照設定的規則判斷其等價性。
固然,上述實現代碼以及測試都是基於特徵值爲name來進行實現的,在現實生活中,好比「居民身份證」來講,判斷兩個對象是否「等價」(便是否爲同一我的),特徵值天然就包括name(名字),sex(性別),age(年齡)等等屬性,考慮到使用居民身份證的頻繁使用以及普遍的應用場景,每一個居民就理所應當地擁有了一個額外的「屬性」: 身份證號。這個獨一無二的值,既實現了每一個對象的區別,又能很方便地進行排序(從而進行檢索等操做)。
因而可知,現實生活中到處體現着「ADT設計者的智慧」...
Java爲程序開發者提供了靈活的設定「特徵值」的方法,所以在設計一種須要的數據類型時,能夠仔細地思考一下兩個對象判斷等價的依據(特徵值)到底是什麼,這樣實現的equals方法,每每給ADT的使用過程帶來了極大的便利。
from Steven Shen
編輯於2018.6.19
修改於2018.6.20