<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 6599字,需瀏覽 14分鐘

           ·

          2021-09-26 19:20

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

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

          它要求我們?nèi)羰侵貙慹quals方法則必須強(qiáng)制重寫hashCode,這是為何呢?

          equals和hashCode方法

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

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

          public static void main(String[] args) {
          String s = "hello";
          String str2 = "world";
          boolean result = s.equals(str2);
          System.out.println(result);
          }
          復(fù)制代碼

          來看看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;
          }
          復(fù)制代碼

          由此可知,String類調(diào)用equals方法比較的將是字符串的內(nèi)容是否相等。又如:

          public static void main(String[] args) {
          Integer a = 500;
          Integer b = 600;
          boolean result = a.equals(b);
          System.out.println(result);
          }
          復(fù)制代碼

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

          public boolean equals(Object obj) {
          if (obj instanceof Integer) {
          return value == ((Integer)obj).intValue();
          }
          return false;
          }
          復(fù)制代碼

          它比較的仍然是值,然而若是沒有重寫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);
          }
          復(fù)制代碼

          即使兩個對象中的值是一樣的,它也是不相等的,因?yàn)樗鼒?zhí)行的是Object類的equals方法:

          public boolean equals(Object obj) {
          return (this == obj);
          }
          復(fù)制代碼

          我們知道,對于引用類型,==比較的是兩個對象的地址值,所以結(jié)果為false,若是想讓兩個內(nèi)容相同的對象在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);
          }
          }
          復(fù)制代碼

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

          public String toString() {
          return getClass().getName() + "@" + Integer.toHexString(hashCode());
          }
          復(fù)制代碼

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

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

          public static void main(String[] args) {
          Map<Object, Object> 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);
          });
          }
          復(fù)制代碼

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

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

          public static void main(String[] args) {
          Map<Object, Object> 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);
          });
          }
          復(fù)制代碼

          它的結(jié)果應(yīng)該是什么呢?是不是和剛才一樣,HashMap中也只有一條數(shù)據(jù)呢?可運(yùn)行結(jié)果卻是這樣的:

          EqualsAndHashCodeTest.User(name=zs, age=20)--1
          EqualsAndHashCodeTest.User(name=zs, age=20)--2
          復(fù)制代碼

          這是為什么呢?這是因?yàn)镠ashMap認(rèn)為這兩個對象并不相同,那我們就重寫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<Object, Object> 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);
          });
          }
          復(fù)制代碼

          運(yùn)行結(jié)果:

          true
          EqualsAndHashCodeTest.User(name=zs, age=20)--1
          EqualsAndHashCodeTest.User(name=zs, age=20)--2
          復(fù)制代碼

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

          public V put(K key, V value) {
          return putVal(hash(key), key, value, false, true);
          }
          復(fù)制代碼

          在存入數(shù)據(jù)之前,HashMap先對key調(diào)用了hash方法:

          static final int hash(Object key) {
          int h;
          return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
          }
          復(fù)制代碼

          該方法會調(diào)用key的hashCode方法并做右移、異或等操作,得到key的hash值,并使用該hash值計算得到數(shù)據(jù)的插入位置,如果當(dāng)前位置沒有元素,則直接插入,如下圖所示:

          既然兩個對象求得的hash值不一樣,那么就會得到不同的插入位置,由此導(dǎo)致HashMap最終存入了兩條數(shù)據(jù)。

          接下來我們重寫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);
          }
          }
          復(fù)制代碼

          那么此時兩個對象計算得到的hash值就會相同:

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

          由于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<Object> 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);
          }
          復(fù)制代碼

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

          總結(jié)

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

          • 兩個相同的對象,其hashCode值一定相同

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

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


          作者:隨身電源
          鏈接:https://juejin.cn/post/7011713684015677471
          來源:掘金
          著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。



          瀏覽 27
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  波多野吉衣AV在线 | 日韩欧美91 | 免费A片国产毛无码A片 | 日本老熟女视频 | 午夜男女羞羞影院 |