亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

重寫(xiě)equals的同時(shí)為何要重寫(xiě)hashCode?

 更新時(shí)間:2021年01月22日 09:10:01   作者:Fred-X  
這篇文章主要給大家介紹了關(guān)于重寫(xiě)equals的同時(shí)為何要重寫(xiě)hashCode的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

結(jié)論

先直接上結(jié)論:

重寫(xiě)equals不一定要重寫(xiě)hashCode,得看情況。如果在沒(méi)使用容器時(shí)其實(shí)是沒(méi)必要的。

如果使用了HashMap等容器,并且使用了自定義對(duì)象作為Key是一定要重寫(xiě)的。

重寫(xiě)equals是為了在業(yè)務(wù)邏輯上判斷實(shí)例之間是否相等。重寫(xiě)hascode是為了讓集合快速判重。

hashCode()與 equals() 的規(guī)定:

1.如果兩個(gè)對(duì)象相等,則 hashcode 一定也是相同的

2.兩個(gè)對(duì)象相等,對(duì)兩個(gè) equals() 方法返回 true

3.兩個(gè)對(duì)象有相同的 hashcode 值,它們也不一定是相等的

4.綜上,equals() 方法被覆蓋過(guò),則 hashCode() 方法也必須被覆蓋

5.hashCode() 的默認(rèn)行為是對(duì)堆上的對(duì)象產(chǎn)生獨(dú)特值。如果沒(méi)有重寫(xiě) hashCode(),則該 class 的兩個(gè)對(duì)象無(wú)論如何都不會(huì)相等(即使這兩個(gè)對(duì)象指向相同的數(shù)據(jù))。

下面舉個(gè)例子說(shuō)明一定要重寫(xiě)。

當(dāng)使用自定義類(lèi)作為HashMap的Key時(shí)put時(shí)

如果只重寫(xiě)equals不重寫(xiě)hashCode會(huì)出現(xiàn)邏輯錯(cuò)誤

先看下面的代碼

public class Test {

  static class Order {
  
    private Long orderId;

    public Order(Long orderId) {
      this.orderId = orderId;
    }

    public Long getOrderId() {
      return orderId;
    }

    public void setOrderId(Long orderId) {
      this.orderId = orderId;
    }

    @Override
    public boolean equals(Object obj) {
      if (obj != null && !(obj instanceof Order)) {
        return false;
      }

      return Objects.equals(this.orderId, ((Order) obj).orderId);
    }

    @Override
    public String toString() {
      return "Order{" +
          "orderId=" + orderId +
          '}';
    }
  }

  public static void main(String[] args) {
    Map<Order, String> map = new HashMap<>();

    Order order1 = new Order(1000000001L);
    Order order2 = new Order(1000000001L);

    map.put(order1, "");
    map.put(order2, "");

    System.out.println(map);
  }
}

運(yùn)行輸出:

{Order{orderId=1000000001}=, Order{orderId=1000000001}=}

在代碼中重寫(xiě)了equals方法,沒(méi)重寫(xiě)hashCode方法。

equals重寫(xiě)的邏輯是:只要orderId相等那么這這兩個(gè)對(duì)象就相等。

而從運(yùn)行結(jié)果來(lái)看,兩個(gè)orderId一致的對(duì)象卻都成功put到了map中。這就是邏輯錯(cuò)誤了,因?yàn)榘凑者壿媮?lái)說(shuō)期望的結(jié)果應(yīng)該只有一個(gè)Order在map中才對(duì)。

我們來(lái)看下HashMap的源碼

只需要看寫(xiě)了注釋的那個(gè)判斷

public V put(K key, V value) {
  return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
   int h;
   return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
        boolean evict) {
  Node<K,V>[] tab; Node<K,V> p; int n, i;
  if ((tab = table) == null || (n = tab.length) == 0)
    n = (tab = resize()).length;
  // 通過(guò)hash算出索引 通過(guò)索引取值==null的話 直接直接插入到索引位置。
  if ((p = tab[i = (n - 1) & hash]) == null)
    tab[i] = newNode(hash, key, value, null);
  else {
    Node<K,V> e; K k;
    if (p.hash == hash &&
      ((k = p.key) == key || (key != null && key.equals(k))))
      e = p;
    else if (p instanceof TreeNode)
      e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
    else {
      for (int binCount = 0; ; ++binCount) {
        if ((e = p.next) == null) {
          p.next = newNode(hash, key, value, null);
          if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
            treeifyBin(tab, hash);
          break;
        }
        if (e.hash == hash &&
          ((k = e.key) == key || (key != null && key.equals(k))))
          break;
        p = e;
      }
    }
    if (e != null) { // existing mapping for key
      V oldValue = e.value;
      if (!onlyIfAbsent || oldValue == null)
        e.value = value;
      afterNodeAccess(e);
      return oldValue;
    }
  }
  ++modCount;
  if (++size > threshold)
    resize();
  afterNodeInsertion(evict);
  return null;
}

通過(guò)源碼我們知道,只要hash碼不一樣的話就可以直接插入到數(shù)組中。然而正因?yàn)槲覀儧](méi)重寫(xiě)hashCode方法,所以調(diào)用的是Object的hashCode方法。而Object的hashCode是使用對(duì)象在堆中的地址通過(guò)算法得出一個(gè)int類(lèi)型的值,既然如此,那剛剛創(chuàng)建的兩個(gè)對(duì)象的int類(lèi)型的值肯定是不同的,所以兩個(gè)Order都可以正常插入到數(shù)組中,從而出現(xiàn)了邏輯錯(cuò)誤。

重寫(xiě)hashCode方法:

public class TestHash {

  static class Order {


    private Long orderId;

    public Order(Long orderId) {
      this.orderId = orderId;
    }

    public Long getOrderId() {
      return orderId;
    }

    public void setOrderId(Long orderId) {
      this.orderId = orderId;
    }

    @Override
    public boolean equals(Object obj) {
      if (obj != null && !(obj instanceof Order)) {
        return false;
      }

      return Objects.equals(this.orderId, ((Order) obj).orderId);
    }

    @Override
    public int hashCode() {
    	// 這里簡(jiǎn)單重寫(xiě)下  實(shí)際開(kāi)發(fā)根據(jù)自己需求重寫(xiě)即可。
      return this.orderId.intValue() >> 2;
    }

    @Override
    public String toString() {
      return "Order{" +
          "orderId=" + orderId +
          '}';
    }
  }

  public static void main(String[] args) {
    Map<Order, String> map = new HashMap<>();

    Order order1 = new Order(1000000001L);
    Order order2 = new Order(1000000001L);

    map.put(order1, "");
    map.put(order2, "");

    System.out.println(map);
  }
}

再次運(yùn)行輸出:

{Order{orderId=1000000001}=}

我們簡(jiǎn)單看下源碼(為了好理解,我只截取了重點(diǎn)代碼):以put order2作為注釋講解。

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
        boolean evict) {
  Node<K,V>[] tab; Node<K,V> p; int n, i;
  if ((tab = table) == null || (n = tab.length) == 0)
    n = (tab = resize()).length;
  // 重寫(xiě)hashCode之后兩個(gè)對(duì)象的orderId相同,hashCode也肯定相同。
  // 通過(guò)hash算出索引 通過(guò)索引取值 有值不進(jìn)入if。
  if ((p = tab[i = (n - 1) & hash]) == null)
    tab[i] = newNode(hash, key, value, null);
  else {
    Node<K,V> e; K k;
    // 由于重寫(xiě)了hashCode 舊對(duì)象的hashCode和新的肯定相等
    if (p.hash == hash &&
    // (k = p.key) == key == false 因?yàn)楸容^的是對(duì)象地址
    // (key != null && key.equals(k)) == true 因?yàn)橹貙?xiě)了equals orderId相等則相等 
      ((k = p.key) == key || (key != null && key.equals(k))))
      // 保存舊Node
      e = p;
    .......
    if (e != null) { // existing mapping for key
      V oldValue = e.value;
      if (!onlyIfAbsent || oldValue == null)
      	// value覆蓋舊Node的值
        e.value = value;
      afterNodeAccess(e);
      return oldValue;
    }
  }
  ........
}

所以order2覆蓋了order1。這就是為什么當(dāng)使用自定義對(duì)象作為HashMap的Key時(shí)如果重寫(xiě)了equals要同時(shí)hashCode。

反過(guò)來(lái)說(shuō):重寫(xiě)了hashCode,equals需要重寫(xiě)嗎?

答案是要的,都要重寫(xiě)!

還是以上面代碼重寫(xiě)的邏輯為例,假設(shè)hashCode相同的兩個(gè)對(duì)象,且已經(jīng)put order1在put時(shí),hash相同,得出的索引也是相同,就可以取到order1,取到之后會(huì)繼續(xù)使用equals比較,假設(shè)沒(méi)有重寫(xiě)的話,那么就是對(duì)象地址比較,結(jié)果肯定是false,那么這個(gè)時(shí)候就發(fā)生了hash碰撞,也就形成了鏈表。

還有在map.get(key)時(shí)也是一樣都會(huì)根據(jù)hashCode找,再判斷equals。

為什么要判斷equals呢?因?yàn)楦鶕?jù)hashCode找到的是一個(gè)鏈表,需要根據(jù)equals在鏈表中找到Key相等的那個(gè)值。

什么場(chǎng)景會(huì)用到自定義類(lèi)做key?

最常見(jiàn)的key是一個(gè)坐標(biāo),比如說(shuō)在地圖的某個(gè)坐標(biāo)放置一個(gè)物體之類(lèi)的。

public class Test {

  static class Coordinate {
    public Coordinate(int x, int y) {
      this.x = x;
      this.y = y;
    }

    private int x;
    private int y;

    public int getX() {
      return x;
    }

    public void setX(int x) {
      this.x = x;
    }

    public int getY() {
      return y;
    }

    public void setY(int y) {
      this.y = y;
    }
  }

  public static void main(String[] args) {
    Map<Coordinate, String> map = new HashMap<>();
    map.put(new Coordinate(22, 99), "手機(jī)");
    map.put(new Coordinate(44, 48), "電腦");
  }
}

總結(jié)

到此這篇關(guān)于重寫(xiě)equals的同時(shí)為何要重寫(xiě)hashCode的文章就介紹到這了,更多相關(guān)重寫(xiě)equals的同時(shí)重寫(xiě)hashCode內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Mapper層繼承BaseMapper<T>需要引入的pom依賴方式

    Mapper層繼承BaseMapper<T>需要引入的pom依賴方式

    這篇文章主要介紹了Mapper層繼承BaseMapper<T>需要引入的pom依賴方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • Mybatis的collection三層嵌套查詢方式(驗(yàn)證通過(guò))

    Mybatis的collection三層嵌套查詢方式(驗(yàn)證通過(guò))

    這篇文章主要介紹了Mybatis的collection三層嵌套查詢方式(驗(yàn)證通過(guò)),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-03-03
  • 深入理解java泛型詳解

    深入理解java泛型詳解

    這篇文章主要介紹了Java中的泛型詳解,什么是泛型,作用以及基礎(chǔ)實(shí)例等,喜歡的朋友可以參考
    2017-04-04
  • Java實(shí)現(xiàn)字節(jié)數(shù)B轉(zhuǎn)化為KB、MB、GB的方法示例【測(cè)試可用】

    Java實(shí)現(xiàn)字節(jié)數(shù)B轉(zhuǎn)化為KB、MB、GB的方法示例【測(cè)試可用】

    這篇文章主要介紹了Java實(shí)現(xiàn)字節(jié)數(shù)B轉(zhuǎn)化為KB、MB、GB的方法,結(jié)合實(shí)例形式分析了java字節(jié)數(shù)的轉(zhuǎn)換運(yùn)算相關(guān)操作技巧,需要的朋友可以參考下
    2017-08-08
  • 將Java程序的輸出結(jié)果寫(xiě)到txt文件中的方法

    將Java程序的輸出結(jié)果寫(xiě)到txt文件中的方法

    今天小編就為大家分享一篇將Java程序的輸出結(jié)果寫(xiě)到txt文件中的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-07-07
  • java.sql.SQLRecoverableException關(guān)閉的連接異常問(wèn)題及解決辦法

    java.sql.SQLRecoverableException關(guān)閉的連接異常問(wèn)題及解決辦法

    當(dāng)數(shù)據(jù)庫(kù)連接池中的連接被創(chuàng)建而長(zhǎng)時(shí)間不使用的情況下,該連接會(huì)自動(dòng)回收并失效,就導(dǎo)致客戶端程序報(bào)“ java.sql.SQLException: Io 異常: Connection reset” 或“java.sql.SQLException 關(guān)閉的連接”異常問(wèn)題,下面給大家分享解決方案,一起看看吧
    2024-03-03
  • Java數(shù)據(jù)溢出代碼詳解

    Java數(shù)據(jù)溢出代碼詳解

    這篇文章主要介紹了Java數(shù)據(jù)溢出的相關(guān)內(nèi)容,包括具體代碼示例,分析比較詳細(xì),希望對(duì)大家有所幫助,感興趣的朋友可以參考下。
    2017-09-09
  • 一行命令同時(shí)修改maven項(xiàng)目中多個(gè)module的版本號(hào)的方法

    一行命令同時(shí)修改maven項(xiàng)目中多個(gè)module的版本號(hào)的方法

    這篇文章主要介紹了一行命令同時(shí)修改maven項(xiàng)目中多個(gè)module的版本號(hào)的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2019-06-06
  • Spring5中的WebClient使用方法詳解

    Spring5中的WebClient使用方法詳解

    這篇文章主要給大家介紹了關(guān)于Spring5中WebClient使用方法的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Spring5具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-11-11
  • Spring?Boot?Yaml配置高級(jí)用法

    Spring?Boot?Yaml配置高級(jí)用法

    這篇文章主要介紹了Spring?Boot?Yaml配置高級(jí)用法,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12

最新評(píng)論