引言

今天中午正在帶著耳機遨遊在程式碼的世界裡,被運營在群裡@了,氣沖沖的反問我最近有刪生產的使用者資料的嗎?我肯定客氣的回答道沒有呀?生產的資料我怎麼能隨隨便便可以刪除,這可是公司的紅線,再說了我也沒有資料庫的刪除許可權啊,不過查詢許可權還是有的。趕緊登上堡壘機,然後去生產資料庫查一下資料,查了一下資料是還在的,嚇死了,資料還在問題就不大了,無非就是應用程式出問題了,趕緊開啟程式碼檢視下,為什麼會少了一條使用者資料,看了下程式碼貌似沒啥問題就是比較簡單的一個邏輯,直接從DB通過分頁查詢資料給到前端,然後前端負責展示,沒有啥複雜的邏輯。心想肯定是前端的問題,肯定是他少展示了資料,立馬把問題也甩給了他,讓他幫忙配合一起看看是否是前端的問題,然後自己也仔細看看程式碼,不到一分鐘前端說他展示的資料沒有問題,都是後端給到的,沒有漏掉展示的。那就是後端的bug了羅。肉眼望去覺得可能出問題的就是分頁導致的資料丟了。不過這個分頁外掛是全公司都在用,應該不至於出問題把,找不到問題只能讓測試幫忙在測試環境試試,看看是否可以復現。

測試環境復現

仔細看了一眼,居然有個去重的方法,去重邏輯也比較簡單就是把list通過轉為set去下重,看下來應該就是這個去重方法有問題了

大致寫了單元測試模仿了下生產的資料,大致邏輯如下:

public static void main(String[] args) {
Set<UserDTO> userSet = new HashSet<>();
UserDTO userDTO = new UserDTO();
userDTO.setId(1);
userDTO.setUserName("java金融"); UserDTO userDTO1 = new UserDTO();
userDTO1.setId(2);
userDTO1.setUserName("java金融");
userSet.add(userDTO);
userSet.add(userDTO1);
System.out.println(userSet.size());
System.out.println(userDTO1.equals(userDTO));
} @Data
static class UserDTO extends BaseDTO {
private String userName;
}
@Data
static class BaseDTO {
private Integer id;
}

我們可以輸出結果set集合的長度是1,user1user2 是相等的,明明兩個userID是不一樣的,為何會相等,我們知道set可以去重

是因為Set的操作,都是通過操作map來實現的,setadd其實就是呼叫mapput方法,mapput方法我相信大家應該都去看過其原始碼,這裡就不詳細再說了,大概流程就是通過key通過hash演算法定位到陣列的下標,先判斷keyhash是否相等,如果相等再去判斷key的value相等,如果都相等就會覆蓋原來的值。我們上面這個例子就是物件的hashvalue都相等導致,但是我們的兩個物件user1user2 應該是不等的,因為ID不等,那為什麼會相等列?我們仔細看下上面的程式碼,我們使用了lombok裡面@Data註解,我們可以看看這個註解幫我們生成了哪些方法





通過上面的對比我們可以看出@Data註解幫我們生成了 注在類上,提供類的get、set、equals、hashCode、canEqual、toString方法,這個註解確實比較方便。上面那個bug 就是因為它生成的equals方法有問題,我們可以把上述程式碼編譯下,然後把class 裡面生成的equals方法拷貝出來看看

通過上述生成的程式碼我們可以看出equals方法只比較了userName這個欄位,也就是當前類的欄位,並沒有去比較父類的欄位,這就是導致兩個物件相等的原因,我們既然找到問題了,那解決問題就比較簡單。

解決問題

  • 手動重寫equalshashCode方法,這種方法肯定是不推薦的,我們既然用了lombok就是為了解放我們的雙手,是程式碼變得更加簡潔。
  • 在比較的類上加上@EqualsAndHashCode(callSuper = true) callSuper = true 會包含父類的equalshashCode方法

    我們可以對比下加上@EqualsAndHashCode(callSuper = true)和沒有加上這個註解生成的equals方法的程式碼差異。



    差異點還是很明顯的,加入了@EqualsAndHashCode(callSuper = true) 會去呼叫父類的equals方法比較,所以這個註解也能夠解決這個問題。
  • 這樣加上註解確實可以解決問題,但是每個類上面都要加上這個註解,這也是個體力活。我們可以再找找其他的方案,例如有沒有比如配置檔案設定下什麼的,然後就能全域性生效了。最終通過查詢資料發現我們我們寫一個lombok.config的配置檔案放在我們專案的根目錄下面,內容寫上lombok.equalsAndHashCode.callSuper = call效果等同於@EqualsAndHashCode(callSuper = true),這樣的話我們就不需要為每個類都去加上這個註釋了,相當於在這個專案下面只要用到了@Data註解的類都會為其加上@EqualsAndHashCode(callSuper = true)通過配置檔案的方式就可以全域性生效。

總結

  • 我們再來回顧下上面的問題,歸根結底還是由於物件的equals方法使用不當引起的,所以我們在如果在判斷自定義物件業務判斷相等的時候需要去重寫下hashCodeequals方法,重寫的時候我們可以通過idea來生成,生成後最好還是去看一眼,看看生成的是否符合我們的業務要求。

  • 我們在工作中操作一些常見的容器類比如Set、Map等來實現一些我們自己的業務,我們還是有必要去看看它們的原始碼的,就比如我們通過Set來進行去重,如果我們是使用的自定義物件的話,如果沒有重寫hashCodeequals方法的話,去重就會去不成功,我們只有瞭解了它,才能真正的去用好它。在關於hashCodeequals 阿里巴巴開發手冊也有明確的說到

  • lombok 用起來還是挺爽的,但是還是有一些細節需要稍微注意下。使用前可以大概的去看看它的官網提供的內容,不然出現莫名其妙的問題你都不知道如何下手。這個就有點類似於我們使用SpringBoot一樣,用起來非常爽,但是如果遇到莫名其妙的bug解決起來就比較頭疼。

  • 最後我們再來回顧幾道關於hashCodeequals的比較常見的面試題?其實如果我們只要真正看過HashMap的原始碼的話,這下面幾個面試題還是非常簡單的。

    什麼情況下需要我們去重寫 方法?

    如果只重寫equals方法不重寫HashCode可以嗎?

    equals ,== 和hashcode()的區別?

結束

  • 由於自己才疏學淺,難免會有紕漏,假如你發現了錯誤的地方,還望留言給我指出來,我會對其加以修正。
  • 如果你覺得文章還不錯,你的轉發、分享、讚賞、點贊、留言就是對我最大的鼓勵。
  • 感謝您的閱讀,十分歡迎並感謝您的關注。