最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

結(jié)論

先直接上結(jié)論:

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

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

重寫equals是為了在業(yè)務(wù)邏輯上判斷實(shí)例之間是否相等。重寫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ú)特值。如果沒有重寫 hashCode(),則該 class 的兩個(gè)對(duì)象無(wú)論如何都不會(huì)相等(即使這兩個(gè)對(duì)象指向相同的數(shù)據(jù))。

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

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

如果只重寫equals不重寫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}=}

在代碼中重寫了equals方法,沒重寫hashCode方法。

equals重寫的邏輯是:只要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的源碼

只需要看寫了注釋的那個(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)槲覀儧]重寫hashCode方法,所以調(diào)用的是Object的hashCode方法。而Object的hashCode是使用對(duì)象在堆中的地址通過(guò)算法得出一個(gè)int類型的值,既然如此,那剛剛創(chuàng)建的兩個(gè)對(duì)象的int類型的值肯定是不同的,所以兩個(gè)Order都可以正常插入到數(shù)組中,從而出現(xiàn)了邏輯錯(cuò)誤。

重寫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)單重寫下  實(shí)際開發(fā)根據(jù)自己需求重寫即可。
      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;
  // 重寫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;
    // 由于重寫了hashCode 舊對(duì)象的hashCode和新的肯定相等
    if (p.hash == hash &&
    // (k = p.key) == key == false 因?yàn)楸容^的是對(duì)象地址
    // (key != null && key.equals(k)) == true 因?yàn)橹貙懥薳quals 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í)如果重寫了equals要同時(shí)hashCode。

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

答案是要的,都要重寫!

還是以上面代碼重寫的邏輯為例,假設(shè)hashCode相同的兩個(gè)對(duì)象,且已經(jīng)put order1在put時(shí),hash相同,得出的索引也是相同,就可以取到order1,取到之后會(huì)繼續(xù)使用equals比較,假設(shè)沒有重寫的話,那么就是對(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ì)用到自定義類做key?

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

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

相關(guān)文章

  • Java中常見死鎖與活鎖的實(shí)例詳解

    Java中常見死鎖與活鎖的實(shí)例詳解

    這篇文章主要介紹了Java中常見死鎖與活鎖的實(shí)例詳解,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-11-11
  • java 創(chuàng)建線程的幾種方式

    java 創(chuàng)建線程的幾種方式

    本文主要介紹了java中創(chuàng)建線程的幾種方式。具有很好的參考價(jià)值,下面跟著小編一起來(lái)看下吧
    2017-02-02
  • idea中springboot整合mybatis找不到mapper接口的原因分析

    idea中springboot整合mybatis找不到mapper接口的原因分析

    這篇文章主要介紹了idea中springboot整合mybatis找不到mapper接口的原因分析及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-01-01
  • idea2019版與maven3.6.2版本不兼容的解決方法

    idea2019版與maven3.6.2版本不兼容的解決方法

    這篇文章主要介紹了idea2019版與maven3.6.2版本不兼容的解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-10-10
  • Eclipse新建web項(xiàng)目流程圖解

    Eclipse新建web項(xiàng)目流程圖解

    這篇文章主要介紹了Eclipse新建web項(xiàng)目流程圖解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-11-11
  • Spring Cloud使用Feign進(jìn)行遠(yuǎn)程調(diào)用的操作指南

    Spring Cloud使用Feign進(jìn)行遠(yuǎn)程調(diào)用的操作指南

    本文介紹了Feign作為聲明式HTTP客戶端在SpringCloud中的使用,從簡(jiǎn)介、對(duì)比RestTemplate的問(wèn)題、使用步驟,到日志配置、性能優(yōu)化和實(shí)際應(yīng)用進(jìn)行了詳細(xì)講解,包括如何通過(guò)Feign簡(jiǎn)化接口調(diào)用,以及解決啟動(dòng)時(shí)找不到FeignClient的問(wèn)題,需要的朋友可以參考下
    2025-02-02
  • 使用spring框架中的組件發(fā)送郵件功能說(shuō)明

    使用spring框架中的組件發(fā)送郵件功能說(shuō)明

    Spring使用的是基本的JavaBean來(lái)完成以前只可能由EJB完成的事情。這篇文章主要介紹了使用spring框架中的組件發(fā)送郵件,需要的朋友可以參考下
    2017-11-11
  • Python爬蟲 12306搶票開源代碼過(guò)程詳解

    Python爬蟲 12306搶票開源代碼過(guò)程詳解

    這篇文章主要介紹了Python爬蟲 12306搶票開源代碼過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-09-09
  • SpringBoot整合Shiro框架,實(shí)現(xiàn)用戶權(quán)限管理

    SpringBoot整合Shiro框架,實(shí)現(xiàn)用戶權(quán)限管理

    Apache Shiro是一個(gè)強(qiáng)大且易用的Java安全框架,執(zhí)行身份驗(yàn)證、授權(quán)、密碼和會(huì)話管理。作為一款安全框架Shiro的設(shè)計(jì)相當(dāng)巧妙。Shiro的應(yīng)用不依賴任何容器,它不僅可以在JavaEE下使用,還可以應(yīng)用在JavaSE環(huán)境中。
    2021-06-06
  • Feign自定義重試策略及超時(shí)時(shí)間詳解

    Feign自定義重試策略及超時(shí)時(shí)間詳解

    這篇文章主要為大家介紹了Feign自定義重試策略及超時(shí)時(shí)間詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-07-07

最新評(píng)論

普宁市| 阿荣旗| 东乡县| 札达县| 清流县| 宿州市| 永州市| 疏勒县| 涿鹿县| 开阳县| 札达县| 临颍县| 舞钢市| 淳化县| 姚安县| 四川省| 五常市| 时尚| 三江| 贡嘎县| 泽库县| 岑巩县| 武冈市| 商洛市| 涟源市| 太湖县| 安达市| 读书| 闽清县| 河间市| 巫山县| 皮山县| 柘城县| 金山区| 吉水县| 绥德县| 石林| 郧西县| 四会市| 红桥区| 宁都县|