1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        面試高頻考點:hashCode與equals

        共 6553字,需瀏覽 14分鐘

         ·

        2021-12-23 14:19

        點擊關注公眾號,Java干貨及時送達

        作者:隨身電源?

        來源:https://juejin.cn/post/7011713684015677471


        先來看阿里巴巴Java開發(fā)手冊中的一段話:

        【強制】關于 hashCode 和 equals 的處理,遵循如下規(guī)則:1) 只要重寫 equals,就必須重寫 hashCode。2) 因為 Set 存儲的是不重復的對象,依據 hashCode 和 equals 進行判斷,所以 Set 存儲的 對象必須重寫這兩個方法。3) 如果自定義對象作為 Map 的鍵,那么必須重寫 hashCode 和 equals。說明:String 重寫了 hashCode 和 equals 方法,所以我們可以非常愉快地使用 String 對象 作為 key 來使用。

        它要求我們若是重寫equals方法則必須強制重寫hashCode,這是為何呢?

        equals和hashCode方法

        我們先來了解一下這兩個方法,它們都來自Object類,說明每一個類中都會有這么兩個方法,那它倆的作用是什么呢?

        首先是equals方法,它是用來比較兩個對象是否相等。對于equals方法的使用,得分情況討論,若是子類重寫了equals方法,則將按重寫的規(guī)則進行比較,比如:

        public?static?void?main(String[]?args)?{
        ????String?s?=?"hello";
        ????String?str2?=?"world";
        ????boolean?result?=?s.equals(str2);
        ????System.out.println(result);
        }

        來看看String類對equals方法的重寫:

        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;
        }

        由此可知,String類調用equals方法比較的將是字符串的內容是否相等。又如:

        public?static?void?main(String[]?args)?{
        ????Integer?a?=?500;
        ????Integer?b?=?600;
        ????boolean?result?=?a.equals(b);
        ????System.out.println(result);
        }

        觀察Integer類的實現(xiàn):

        public?boolean?equals(Object?obj)?{
        ????if?(obj?instanceof?Integer)?{
        ????????return?value?==?((Integer)obj).intValue();
        ????}
        ????return?false;
        }

        它比較的仍然是值,然而若是沒有重寫equals方法:

        @AllArgsConstructor
        static?class?User?{
        ????private?String?name;
        ????private?Integer?age;
        }

        public?static?void?main(String[]?args)?{
        ????User?user?=?new?User("zs",?20);
        ????User?user2?=?new?User("zs",?20);
        ????boolean?result?=?user.equals(user2);
        ????System.out.println(result);
        }

        即使兩個對象中的值是一樣的,它也是不相等的,因為它執(zhí)行的是Object類的equals方法:

        public?boolean?equals(Object?obj)?{
        ????return?(this?==?obj);
        }

        我們知道,對于引用類型,==比較的是兩個對象的地址值,所以結果為false,若是想讓兩個內容相同的對象在equals后得到true,則需重寫equals方法:

        @AllArgsConstructor
        static?class?User?{
        ????private?String?name;
        ????private?Integer?age;

        ????@Override
        ????public?boolean?equals(Object?o)?{
        ????????if?(this?==?o)?return?true;
        ????????if?(o?==?null?||?getClass()?!=?o.getClass())?return?false;
        ????????User?user?=?(User)?o;
        ????????return?Objects.equals(name,?user.name)?&&?Objects.equals(age,?user.age);
        ????}
        }

        再來聊一聊hashCode方法,它是一個本地方法,用來返回對象的hash碼值,通常情況下,我們都不會使用到這個方法,只有Object類的toString方法使用到了它:

        public?String?toString()?{
        ????return?getClass().getName()?+?"@"?+?Integer.toHexString(hashCode());
        }

        為什么只要重寫了equals方法,就必須重寫hashCode

        了解兩個方法的作用后,我們來解決本篇文章的要點,為什么只要重寫了equals方法,就必須重寫hashCode呢?這是針對一些使用到了hashCode方法的集合而言的,比如HashMap、HashSet等,先來看一個現(xiàn)象:

        public?static?void?main(String[]?args)?{
        ????Map?map?=?new?HashMap<>();
        ????String?s1?=?new?String("key");
        ????String?s2?=?new?String("key");

        ????map.put(s1,?1);
        ????map.put(s2,?2);
        ????map.forEach((k,?v)?->?{
        ????????System.out.println(k?+?"--"?+?v);
        ????});
        }

        這段程序的輸出結果是:key--2,原因是HashMap中的key不能重復,當有重復時,后面的數據會覆蓋原值,所以HashMap中只有一個數據,那再來看下面一段程序:

        @AllArgsConstructor
        @ToString
        static?class?User?{
        ????private?String?name;
        ????private?Integer?age;
        }

        public?static?void?main(String[]?args)?{
        ????Map?map?=?new?HashMap<>();
        ????User?user?=?new?User("zs",?20);
        ????User?user2?=?new?User("zs",?20);

        ????map.put(user,?1);
        ????map.put(user2,?2);
        ????map.forEach((k,?v)?->?{
        ????????System.out.println(k?+?"--"?+?v);
        ????});
        }

        它的結果應該是什么呢?是不是和剛才一樣,HashMap中也只有一條數據呢?可運行結果卻是這樣的:

        EqualsAndHashCodeTest.User(name=zs,?age=20)--1
        EqualsAndHashCodeTest.User(name=zs,?age=20)--2

        這是為什么呢?這是因為HashMap認為這兩個對象并不相同,那我們就重寫equals方法:

        @AllArgsConstructor
        @ToString
        static?class?User?{
        ????private?String?name;
        ????private?Integer?age;

        ????@Override
        ????public?boolean?equals(Object?o)?{
        ????????if?(this?==?o)?return?true;
        ????????if?(o?==?null?||?getClass()?!=?o.getClass())?return?false;
        ????????User?user?=?(User)?o;
        ????????return?Objects.equals(name,?user.name)?&&?Objects.equals(age,?user.age);
        ????}
        }

        public?static?void?main(String[]?args)?{
        ????Map?map?=?new?HashMap<>();
        ????User?user?=?new?User("zs",?20);
        ????User?user2?=?new?User("zs",?20);

        ????System.out.println(user.equals(user2));

        ????map.put(user,?1);
        ????map.put(user2,?2);
        ????map.forEach((k,?v)?->?{
        ????????System.out.println(k?+?"--"?+?v);
        ????});
        }

        運行結果:

        true
        EqualsAndHashCodeTest.User(name=zs,?age=20)--1
        EqualsAndHashCodeTest.User(name=zs,?age=20)--2

        兩個對象判斷是相同的,但HashMap中仍然存放了兩條數據,說明HashMap仍然認為這是兩個不同的對象。這其實涉及到HashMap底層的原理,查看HashMap的put方法:

        public?V?put(K?key,?V?value)?{
        ????return?putVal(hash(key),?key,?value,?false,?true);
        }

        在存入數據之前,HashMap先對key調用了hash方法:

        static?final?int?hash(Object?key)?{
        ????int?h;
        ????return?(key?==?null)???0?:?(h?=?key.hashCode())?^?(h?>>>?16);
        }

        該方法會調用key的hashCode方法并做右移、異或等操作,得到key的hash值,并使用該hash值計算得到數據的插入位置,如果當前位置沒有元素,則直接插入,如下圖所示:既然兩個對象求得的hash值不一樣,那么就會得到不同的插入位置,由此導致HashMap最終存入了兩條數據。

        接下來我們重寫User對象的hashCode和equals方法:

        @AllArgsConstructor
        @ToString
        static?class?User?{
        ????private?String?name;
        ????private?Integer?age;

        ????@Override
        ????public?boolean?equals(Object?o)?{
        ????????if?(this?==?o)?return?true;
        ????????if?(o?==?null?||?getClass()?!=?o.getClass())?return?false;
        ????????User?user?=?(User)?o;
        ????????return?Objects.equals(name,?user.name)?&&?Objects.equals(age,?user.age);
        ????}

        ????@Override
        ????public?int?hashCode()?{
        ????????return?Objects.hash(name,?age);
        ????}
        }

        那么此時兩個對象計算得到的hash值就會相同:當通過hash計算得到相同的插入位置后,user2便會發(fā)現(xiàn)原位置上已經有數據了,此時將觸發(fā)equals方法,對兩個對象的內容進行比較,若相同,則認為是同一個對象,再用新值覆蓋舊值,所以,我們也必須重寫equals方法,否則,HashMap始終會認為兩個new 出來的對象是不相同的,因為它倆的地址值不可能一樣。

        由于String類重寫了hashCode和equals方法,所以,我們可以放心大膽地使用String類型作為HashMap的key。

        在HashSet中,同樣會出現(xiàn)類似的問題:

        @AllArgsConstructor
        @ToString
        static?class?User?{
        ????private?String?name;
        ????private?Integer?age;
        }

        public?static?void?main(String[]?args)?{
        ????Set?set?=?new?HashSet<>();
        ????User?user?=?new?User("zs",?20);
        ????User?user2?=?new?User("zs",?20);

        ????set.add(user);
        ????set.add(user2);

        ????set.forEach(System.out::println);
        }

        對于內容相同的兩個對象,若是沒有重寫hashCode和equals方法,則HashSet并不會認為它倆重復,所以會將這兩個User對象都存進去。

        總結

        hashCode的本質是幫助HashMap和HashSet集合加快插入的效率,當插入一個數據時,通過hashCode能夠快速地計算插入位置,就不需要從頭到尾地使用equlas方法進行比較,但為了不產生問題,我們需要遵循以下的規(guī)則:

        • 兩個相同的對象,其hashCode值一定相同
        • 若兩個對象的hashCode值相同,它們也不一定相同

        所以,如果不重寫hashCode方法,則會發(fā)生兩個相同的對象出現(xiàn)在HashSet集合中,兩個相同的key出現(xiàn)在Map中,這是不被允許的,綜上所述,在日常的開發(fā)中,只要重寫了equals方法,就必須重寫hashCode。


        1、Log4j2維護者吐槽沒工資還要挨罵,GO安全負責人建議開源作者向公司收費
        2、太難了!讓程序員崩潰的8個瞬間
        3、2021年程序員們都在用的神級數據庫
        4、Windows重要功能被閹割,全球用戶怒噴數月后微軟終于悔改
        5、牛逼!國產開源的遠程桌面火了,只有9MB 支持自建中繼器!
        6、摔到老三的 Java,未來在哪?
        7、真香!用 IDEA 神器看源碼,效率真高!

        點分享

        點收藏

        點點贊

        點在看

        瀏覽 39
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

          <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            亚洲乱码中文字幕 | 丰满少妇叫我吸她大乳 | 国产黄网站在线观看 | 大力精品影院久久久久久漫画 | 久久成人小电影 | 无码任你躁久久久久久在少妇 | 美女黄色真播 | 国产成人高潮毛片 | 豆花无码视频一区二区 | 国产精品国产三级国产aⅴ入口 |