1. 程式人生 > >理解Java中的hashCode和equals 方法

理解Java中的hashCode和equals 方法

err array size tex nat 什麽 map 交流群 培訓

在Java裏面所有的類都直接或者間接的繼承了java.lang.Object類,Object類裏面提供了11個方法,如下:

Java代碼 技術分享圖片

  1. ````

  2. 1,clone()

  3. 2,equals(Object obj)

  4. 3,finalize()

  5. 4,getClass()

  6. 5,hashCode()

  7. 6,notify()

  8. 7,notifyAll()

  9. 8,toString()

  10. 9,wait()

  11. 10,wait(long timeout)

  12. 11,wait(long timeout, int nanos)

  13. ````



這裏面我們常用的方法有三個:

Java代碼 技術分享圖片

  1. ````

  2. toString()

  3. equals(Object obj)

  4. hashCode()

  5. ````




toString方法,相信用過Java的人都不會陌生,默認打印的是:類名@十六進制的hashCode,源碼中定義如下:

Java代碼 技術分享圖片

  1. ````

  2. public String toString() {

  3. return getClass().getName() + "@" + Integer.toHexString(hashCode());

  4. }

  5. ````

在經過重寫後,我們可以打印一個class的所有屬性,這樣在打印log或調試時比較方便。

下面重點介紹下hashCode和equals方法:





(1)equals方法,在JDK默認的情況下比較的是對象的內存地址,源碼如下:

Java代碼 技術分享圖片

  1. ````

  2. public boolean equals(Object obj) {

  3. return (this == obj);

  4. }

  5. ````



(2)hashcode方法,默認情況下返回的是一個唯一的整數,代表該實例的內存地址,註意這個數字
並不是實際的內存地址,Java是沒辦法直接獲取內存地址的,必須得由C或者C++獲取,所以這個方法是用
native修飾的

Java代碼 技術分享圖片

  1. ````

  2. public native int hashCode();

  3. ````





由於默認情況下,equals方法比較的是內存地址,而在實際開發中,我們判斷兩個對象是否相等,一般都是根據對象的屬性來判斷的,
所以需要重寫這個方法,不然的話,是沒辦法比較的。舉例如下:


定義的類如下:

Java代碼 技術分享圖片

  1. ````

  2. public class Hero {

  3. private String id;

  4. private String name;

  5. public Hero() {

  6. }

  7. public Hero(String id, String name) {

  8. this.id = id;

  9. this.name = name;

  10. }

  11. public String getId() {

  12. return id;

  13. }

  14. public void setId(String id) {

  15. this.id = id;

  16. }

  17. public String getName() {

  18. return name;

  19. }

  20. public void setName(String name) {

  21. this.name = name;

  22. }

  23. }

  24. ````

直接比較兩個對象,結果是不相等的:

Java代碼 技術分享圖片

  1. ````

  2. `````` Hero h1=new Hero("1","張飛");

  3. Hero h2=new Hero("1","張飛");

  4. //false

  5. System.out.println(h1.equals(h2));

  6. ````




因為他們的內存地址是不同的,所以結果是false,如果我們想要認為他是相等的,那麽就需要重寫
equals方法:

Java代碼 技術分享圖片

  1. ````

  2. @Override

  3. public boolean equals(Object o) {

  4. if (this == o) return true;//如果內存地址相等,則兩個對象必定相等

  5. if (o == null || getClass() != o.getClass()) return false;//如果有一個為null或者class不一樣,認為必不相等

  6. Hero hero = (Hero) o;//

  7. if (id != null ? !id.equals(hero.id) : hero.id != null) return false;//比較id,不相等就返回false

  8. return name != null ? name.equals(hero.name) : hero.name == null;//上面的條件通過後比較name,如果相等返回true,不相等返回false

  9. }

  10. ````



在重寫equals方法後,我們在比較兩個對象,發現就相等了

Java代碼 技術分享圖片

  1. ````

  2. ``` Hero h1=new Hero("1","張飛");

  3. Hero h2=new Hero("1","張飛");

  4. //true

  5. System.out.println(h1.equals(h2));

  6. ````



接著我們看第二個例子,將其放入ArrayList中,然後判斷是否存在,發現也生效了:

Java代碼 技術分享圖片

  1. ````

  2. ` Hero h1=new Hero("1","張飛");

  3. List<Hero> heros=new ArrayList<Hero>();

  4. heros.add(h1);

  5. //true

  6. System.out.println(heros.contains(new Hero("1","張飛")));

  7. ````



到目前為止,我們還沒有對hashCode進行操作,那麽大家可能會有一個疑問,既然都有equals方法比較了,為啥還需要hashCode方法呢? 別著急,繼續看下面的例子:

我們都知道在Java裏面HashSet類,去無序去重的,下面看一下,只重寫equasl方法能不能實現對class的去重:

Java代碼 技術分享圖片

  1. ````

  2. ` Hero h1=new Hero("1","張飛");

  3. Hero h2=new Hero("1","張飛");

  4. Set<Hero> heros=new HashSet<Hero>();

  5. heros.add(h1);

  6. heros.add(h2);

  7. //2

  8. System.out.println(heros.size());

  9. //false

  10. System.out.println(heros.contains(new Hero("1","張飛")));

  11. ````




從上面的結果看,並沒有去重,有的小夥伴會說為啥時string類型的時候就能去重?這是因為Stirng類默認已經重寫了equals和hashcode方法,當然所有的基本類型都重寫這兩個方法了。

接著回到上面的問題,為什麽在HashSet中去重失效了呢?


其實,不止是HashSet,在HashMap和Hashtable等等所有使用hash相關的數據結構中,如果使用時不重寫hashcode,那麽就沒法比較對象是否存在。


這其實與HashMap存儲原理相關(HashSet底層用的也是HashMap),HashMap在存儲時其實是采用了數組+鏈表的存儲結構,數組
中的每一個元素,我們可以理解成是一個buckets(桶),桶裏面的結構是鏈表,而數據是如何分到各個桶裏面其實與hashCode有很大關系,只有hashCode一樣的
對象才能被分到一個桶裏。存的時候,遍歷鏈表判斷是否存在,如果存在就不覆蓋,如果不存在就把該元素放在鏈表的頭,把next指向上一次的頭,而讀取的時候先定位到桶裏,然後遍歷
鏈表找到該元素即可。



理解了這些,就明白了為啥上面的例子中,去重失效了。就是因為他們的hashCode不一樣,導致被分到不同的桶裏面了,自然就沒法去重了。

重寫hashCode之後,再看結果:

Java代碼 技術分享圖片

  1. ````

  2. @Override

  3. public int hashCode() {

  4. return Integer.parseInt(id);

  5. }

  6. ````



Java代碼 技術分享圖片

  1. ````

  2. · Hero h1=new Hero("1","張飛");

  3. Hero h2=new Hero("1","張飛");

  4. Set<Hero> heros=new HashSet<Hero>();

  5. heros.add(h1);

  6. heros.add(h2);

  7. //1

  8. System.out.println(heros.size());

  9. //true

  10. System.out.println(heros.contains(new Hero("1","張飛")));

  11. ````



這下結果就對了。


那麽問題來了,為啥需要hashCode? 因為在HashSet中,可以存儲大量的元素,如果沒有hashCode,那麽每次就得全量的比較每一個元素,來判斷
是否存在,這樣以來效率肯定極低,而有了hashCode之後,只需要找到該數據的鏈表,然後遍歷這個鏈表的數據即可,這樣以來效率
就大大提升。

一個Java交流平臺分享給你們,讓你在實踐中積累經驗掌握原理。如果你想拿高薪,想突破瓶頸,想跟別人競爭能取得優勢的,想進BAT但是有擔心面試不過的,可以加Java學習交流群:642830685


註:加群要求


1、大學學習的是Java相關專業,畢業後面試受挫,找不到對口工作


2、在公司待久了,現在過得很安逸,但跳槽時面試碰壁。需要在短時間內進修、跳槽拿高薪的


3、參加過線下培訓後,知識點掌握不夠深刻,就業困難,想繼續深造


4、已經在Java相關部門上班的在職人員,對自身職業規劃不清晰,混日子的


5、有一定的C語言基礎,接觸過java開發,想轉行的



總結:

(1)如果兩個對象相等,那麽他們必定有相同的hashcode

(2)如果兩個對象的hashcode相等,他們卻不一定相等

(3)重寫equasl方法時,一定要記得重寫hashcode方法,尤其用在hash類的數據結構中。


理解Java中的hashCode和equals 方法