日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java拾遗:001 - 重写 equals 和 hashCode 方法

發(fā)布時間:2025/6/15 java 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java拾遗:001 - 重写 equals 和 hashCode 方法 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

2019獨角獸企業(yè)重金招聘Python工程師標準>>>

重寫equals方法

在Java中Object類是一個具體類,但它設計的主要目的是為了擴展,所以它的所有非final方法,都被設計成可覆蓋(override)的。但任何一個子類在覆蓋這些方法時都應遵守一些通用約定,否則就會在使用中引起各種問題。

equals方法定義于Object類中,用于比較兩個對象是否相等,說起比較相等我們也常用==符號來比較,但兩者有什么區(qū)別呢?

equals方法與==的區(qū)別

一般來說==用于比較基本類型值是否相等,如:int、float等,或者用于比較對象引用地址是否相同(兩個引用指向同一對象),而equals方法則由程序員自己來實現(xiàn)(JDK源碼里的類是由JDK的開發(fā)者實現(xiàn)的,同樣也是程序員自己實現(xiàn)的)來比較兩個對象是否相等(強調(diào)一下,這里說的是相等而非相同)的。后者包含前者,即:使用==比較相同的對象equals方法一定返回true。

什么時候需要重寫equals方法?

通常我們需要在代碼中實現(xiàn)判斷一個對象是否等于另外一個對象,或者需要將對象加入集合時,會需要使用equals方法來提供判斷邏輯(集合中添加元素時會使用contains方法來判斷添加對象是否已存在于集合中,內(nèi)部調(diào)用的判斷方法即為equals方法)。

equals方法的等價關系

重寫equals方法看似很簡單,但很許多方式會導致錯誤,并且造成嚴重后果,所以Java規(guī)范對對重寫equals方法定義了一些約定(非強制,但應盡量遵守),即:equals方法需要實現(xiàn)等價關系(equivalence relation)。

  • 自反性(reflexive),對于任何非null的引用值x,x.equals(x)必須返回true。
  • 對稱性(symmetric),對于任何非null的引用值x和y,當且僅當y.equals(x)返回true時,x.equals(y)必須返回true。
  • 傳遞性(transitive),對于任何非null的引用值x、y和z,如果x.equals(y)返回true且y.equals(z)也返回true,那么x.equals(z)也必須返回true。
  • 對于任何非null的引用值x,x.equals(null)必須返回false。 你當然可以無視這些約定,但當你發(fā)現(xiàn)你的程序表現(xiàn)不正常或者未達到預期的時候,你可以會很難找到失敗的根源(出自《Effective Java》)。

重寫equals方法的最佳實踐

如果說上面的條目還不是很具體的話,下面通過一些示例來闡述上面的條目。 首先我們有一個Employee類和Manager類,包含幾個域對象(屬性)

public class Employee {private String name;private Double salary;private Date joinDate;// getter / setter ... }public class Manager extends Employee {private Double bonus;// getter / setter ... }

如果我們不重寫equals方法

@Testpublic void equals_1() {// 有兩個Employee對象,我們假定如果姓名與薪資相等即認為兩個對象相等Employee x = new Employee();x.setName("Jane");x.setSalary(3500.0);Employee y = new Employee();y.setName("Jane");y.setSalary(3500.0);// 此時我們沒有重寫equals方法,此時使用的equals方法由Object提供,只簡單比較兩個對象是否相同assertTrue(x.equals(x));assertFalse(x.equals(y));}

會看到對象x.equals(x)返回true而x.equals(y)返回false,而根據(jù)假定條件應返回true,所以Object里的equals方法顯然不夠用,我們需要自定義equals方法。 而在實現(xiàn)自定義equals方法時,第一條約定自反性,這一條很難無意識地違反這一條(如果違反了,你在向集合中添加元素時就會重復添加),但通常我們還是實現(xiàn)該約定,這通常是一種性能優(yōu)化的方式(如果兩個比較對象是同一個對象,就返回true,后面的比較邏輯就省略了)。

@Overridepublic boolean equals(Object obj) {// 這里使用==顯示判斷比較對象是否是同一對象if (this == obj) {return true;}// 對于任何非null的引用值x,x.equals(null)必須返回falseif (obj == null) {return false;}// TODO 核心域比較return false;}

注意@Override注解,重寫方法時務必加上該注解,IDE會幫我們檢查是否是重寫父類方法,否則可能實現(xiàn)的是重載方法(改變了方法簽名),導致后面運行出錯而找不到問題的原因。

上面實現(xiàn)了自反性,下面繼續(xù)實現(xiàn)對稱性

@Overridepublic boolean equals(Object obj) {// 這里使用==顯示判斷比較對象是否是同一對象if (this == obj) {return true;}// 對于任何非null的引用值x,x.equals(null)必須返回falseif (obj == null) {return false;}// 通過 instanceof 判斷比較對象類型是否合法if (!(obj instanceof Employee)) {return false;}// 對象類型強制轉換,如果核心域比較相等,則返回true,否則返回false// 強制類型轉換前,必須使用instanceof判斷,避免代碼拋出ClassCastException異常Employee other = (Employee) obj;return (this.name == other.name || (this.name != null && this.name.equals(other.name)))&& (this.salary == other.salary || (this.salary != null && this.salary.equals(other.salary)));}

測試代碼證明equals方法實現(xiàn)了對稱性

@Testpublic void equals_2() {Employee x = new Employee();x.setName("Jane");x.setSalary(3500.0);Manager y = new Manager();y.setName("Jane");y.setSalary(3500.0);assertTrue(x.equals(y));assertTrue(y.equals(x));}

但在使用instanceof的時候需要注意,如果所有子類擁有統(tǒng)一的語義時使用instanceof 檢查,如果要求比較目標類必須與當前類為同一類,可以使用this.getClass() == obj.getClass()來比較。

使用JDK7提供的工具類優(yōu)化代碼

我們在寫equals方法時,經(jīng)常需要判斷屬性值是否為空,非空時才比較目標對象的相同屬性值是否相等,而在JDK8中提供了Objects的工具類,可以幫我們簡化這部分代碼

@Overridepublic boolean equals(Object obj) {// 這里使用==顯示判斷比較對象是否是同一對象if (this == obj) {return true;}// 對于任何非null的引用值x,x.equals(null)必須返回falseif (obj == null) {return false;}// 通過 instanceof 判斷比較對象類型是否合法if (!(obj instanceof Employee)) {return false;}// 對象類型強制轉換,如果核心域比較相等,則返回true,否則返回falseEmployee other = (Employee) obj;// 如果兩者相等,返回true(含兩者皆空的情形),否則比較兩者值是否相等return Objects.equals(this.name, other.name)&& Objects.equals(this.salary, other.salary);}

另外該類還提供了深度比較的方法deepEquals,對于屬性為引用類型比較使用。

重寫hashCode方法

通常來說,覆寫equals方法時必須要覆寫hashCode方法,但這是為什么呢?

HashCode(散列碼)是什么?

首先來說一下HashCode是什么,HashCode中文翻譯為哈希碼或散列碼,由哈希算法,將對象映射為一個整型數(shù)值。在Java中一般用于HashMap、HashSet、HashTable集合類中。

為什么重寫equals方法同時需要重寫hashCode方法?

上面說到HashMap等哈希類型集合對類,由于HashMap的底層存儲結構為數(shù)組結構,每個元素又是一個鏈表,而數(shù)組的下標即為HashCode,所以相同HashCode的對象會被存放在同個鏈表中。所以如果重寫equals方法而不重寫hashCode方法時,就會導致將兩個相等的對象(equals判斷相等)加入HashMap時,因為返回不同的HashCode而分在了不同的哈希桶中,造成重復添加元素(同一個哈希桶會通過equals方法判斷是否重復)。

@Testpublic void hashCode_1() {Employee x = new Employee();x.setName("Jane");x.setSalary(3500.0);Employee y = new Employee();y.setName("Jane");y.setSalary(3500.0);// HashSet底層由HashMap實現(xiàn)HashSet<Employee> sets = new HashSet<>();sets.add(x);sets.add(y);assertEquals(2, sets.size());}

上述測試代碼證明了這一點,預期添加兩個相等對象,集合中應只有一個元素才對。

怎樣編寫一個好的hashCode方法?

相等的對象必須具有相等的HashCode,但反過來卻不一定,因為存在哈希碰撞,通俗地說就是不同對象(也不相等),可能生成的HashCode是相同的,而發(fā)生哈希碰撞的幾率則是由哈希算法決定的。一般來說發(fā)生哈希碰撞幾率越大,性能就越差,所以一個好的hashCode方法因盡可能的減少哈希碰撞的幾率。

業(yè)界并沒有最佳的哈希碼生成算法(沒有最好,只有最合適),這里參考《Core Java》和《Effective Java》給出一個參考實現(xiàn)

@Overridepublic int hashCode() {int r = 17;r = 31 * r + this.name.hashCode();r = 31 * r + this.salary.hashCode();return r;}

使用JDK7中提供的工具類優(yōu)化

同樣Objects類也提供了hashCode的工具方法,底層代碼使用了Arrays類的hashCode生成方法

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

下面是Arrays類的hashCode方法代碼

public static int hashCode(Object a[]) {if (a == null)return 0;int result = 1;for (Object element : a)result = 31 * result + (element == null ? 0 : element.hashCode());return result;}

String類中的hashCode方法

public int hashCode() {int h = hash;if (h == 0 && value.length > 0) {char val[] = value;for (int i = 0; i < value.length; i++) {h = 31 * h + val[i];}hash = h;}return h;}

JDK中在編寫hashCode方法時,大量使用了31這個魔法數(shù)字,據(jù)《Effective Java》描述該數(shù)字有一個很好的特性:用移位和減法代替乘法,可以得到更好的性能31 * i == (i << 5) - i。

散列碼的性能優(yōu)化

通常不建議會被修改的屬性參與HashCode計算(實際難以避免),因為這會引起HashCode的變化,對于已加入HashMap的對象,不會重新分配存儲位置,而導致一些問題。

對于一些比較復雜的對象,其HashCode的計算是一件非常消耗資源的事,一個簡單的辦法就是對其HashCode進行緩存,比如在類中添加一個屬性,記錄該HashCode,HashCode可以在類初始化時生成,也可以在第一次調(diào)用hashCode方法時生成,這要視具體應用而定。但前提條件是參與計算HashCode的屬性值不能修改。

結語

有很多約定不是強制的,但實際開發(fā)過程中卻應盡量遵循,這些“最佳實踐”會減少很多代碼中潛在的Bug,或者提升代碼性能。

參考資料

  • 《Core Java》
  • 《Effective Java》
  • 《編寫高質(zhì)量代碼:改善Java程序的151個建議》

轉載于:https://my.oschina.net/zhanglikun/blog/1921429

總結

以上是生活随笔為你收集整理的Java拾遗:001 - 重写 equals 和 hashCode 方法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。