Java如何比較兩個(gè)對(duì)象并獲取不相等的字段詳解
?寫在前面
在工作中,我們經(jīng)常會(huì)遇到這樣的需求——比較兩個(gè)對(duì)象是否相等,如果不相等的話,取出不相等的字段。
以下這些場(chǎng)景都需要我們對(duì)一個(gè)對(duì)象進(jìn)行比較:
- 數(shù)據(jù)比對(duì)
- 做單元測(cè)試斷言對(duì)象是否相等
- 前端要求對(duì)不相等的字段進(jìn)行高亮顯示
這種需求其實(shí)是非常簡(jiǎn)單的,但是如何優(yōu)雅地解決這一類需求呢?
通常的做法是重寫對(duì)象的 equals 方法。但是重寫 equals 方法有很多缺點(diǎn),例如:
- 每次對(duì)象屬性有變更,一定要記得再重寫(放心,你一定會(huì)忘記的)
- 每個(gè)對(duì)象只能有一個(gè) equals 方法,但是可能你會(huì)需要不同的比對(duì)規(guī)則
- 只能對(duì)比兩個(gè)對(duì)象是否相等,無(wú)法具體知道哪個(gè)屬性不等
- 自動(dòng)生成的 equals 方法無(wú)法基于 getter 方法進(jìn)行比對(duì)
- 對(duì)象來(lái)自第三方依賴,無(wú)法重寫 equals 方法
因此,實(shí)現(xiàn)一個(gè)通用的比對(duì)器可以減少很多不必要的麻煩,幫助我們很好地完成這一類的需求。
緣起
我是在做數(shù)據(jù)同步的時(shí)候有這個(gè)需求,我要將數(shù)據(jù)庫(kù)的數(shù)據(jù)通過(guò)一定的規(guī)則導(dǎo)入到 ES 中,導(dǎo)入完成之后,如何比對(duì)兩邊的數(shù)據(jù)是否一致呢?這時(shí)候一個(gè)好用的比對(duì)器就是我非常好的幫手。
另外,我在做單元測(cè)試的時(shí)候發(fā)現(xiàn),經(jīng)常會(huì)需要將被測(cè)方法的返回值和期望的結(jié)果做 assertEquals 斷言這時(shí)這個(gè)比對(duì)器也非常有幫助。我發(fā)現(xiàn)很多同事經(jīng)常會(huì)遇到類似的需求。
于是,我找時(shí)間自己實(shí)現(xiàn)了一下。
實(shí)現(xiàn)
使用反射對(duì)傳入的對(duì)象進(jìn)行比對(duì),提供了基于字段的比較器和基于 Getter 方法的對(duì)比器,并且充分考慮擴(kuò)展性,使用者可以重寫字段的比對(duì)規(guī)則。功能相對(duì)簡(jiǎn)單,代碼實(shí)現(xiàn)也不難,而且做了很多注釋,具體實(shí)現(xiàn)可以直接查看源碼。
項(xiàng)目地址:https://github.com/dadiyang/equator
UML圖:
使用方法
因?yàn)橐呀?jīng)上傳到了 maven 倉(cāng)庫(kù)中,我們使用非常方便:
添加 maven 依賴
<dependency> <groupId>com.github.dadiyang</groupId> <artifactId>equator</artifactId> <version>1.0.3</version> </dependency>
初始化并調(diào)用方法
Equator equator = new GetterBaseEquator(); User user1 = new User(...); User user2 = new User(...); // 判斷屬性是否完全相等 equator.isEquals(user1, user2); // 獲取不同的屬性 List<FieldInfo> diff = equator.getDiffFields(user1, user2);
擴(kuò)展
我們可以通過(guò)繼承并重寫 isFieldEquals 方法自定義比對(duì)規(guī)則,例如我們?cè)谧鰡卧獪y(cè)試的時(shí)候,對(duì)于 Date 類型的字段的比對(duì),通常數(shù)據(jù)庫(kù)不保存毫秒數(shù),而我們 new 出來(lái)的 Date 對(duì)象則包含了毫秒數(shù),因此我們?cè)趯?duì)包含 Date 類型字段的對(duì)象做比對(duì)的時(shí)候需要忽略日期的毫秒數(shù)。這時(shí)就可以通過(guò)重寫isFieldEquals 方法來(lái)自定義了:
/** * 日期在數(shù)據(jù)庫(kù)不保存毫秒數(shù),因此需要特殊處理,比對(duì)時(shí)間時(shí),忽略毫秒數(shù) * * @author dadiyang * @date 2019/3/23 */ public class MmInsensitiveEquator extends GetterBaseEquator { @Override protected boolean isFieldEquals(FieldInfo fieldInfo) { if (fieldInfo.getFirstVal() instanceof Date) { Date first = (Date) fieldInfo.getFirstVal(); Date second = (Date) fieldInfo.getSecondVal(); if (Objects.equals(first, second)) { return true; } // 忽略毫秒數(shù) return Objects.equals(Math.round(first.getTime() / 1000), Math.round(second.getTime() / 1000)); } return super.isFieldEquals(fieldInfo); } }
后記
對(duì)象比對(duì)是一個(gè)非常小的需求,通常我們只會(huì)寫一個(gè)工具類來(lái)完成。但是寫一個(gè)工具類在各個(gè)項(xiàng)目間隨處拷貝,非常不優(yōu)雅,給整個(gè)團(tuán)隊(duì)帶來(lái)很多不必要的維護(hù)成本。而且擴(kuò)展性比較差,有任何差異就需要寫很多代碼去實(shí)現(xiàn)。
這時(shí),如果我們從具體解決某一個(gè)需求的視角上升到解決一類需求,那么就能想出更加通用和優(yōu)雅的解決方案了。一個(gè)個(gè)具體的需求是無(wú)窮無(wú)盡的,以有限的人生去解決無(wú)限的需求,殆矣;但是將它們歸類之后,我們會(huì)發(fā)現(xiàn),需求的種類是有限的。
附:JAVA判斷(獲?。﹥蓚€(gè)相同對(duì)象不同的數(shù)據(jù)
項(xiàng)目中需要獲取修改前和修改后的不同數(shù)據(jù)并進(jìn)行保存。
不知道高大上的做法,就寫個(gè)工具類。
package com.shiyan.utils.object; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; import org.apache.commons.lang3.StringUtils; public class GetDifference { public static Map<String, String> getUser(Object a, Object b) throws IllegalArgumentException, IllegalAccessException { Map<String, String> map = new HashMap<String, String>(); Field[] declaredFields2 = a.getClass().getDeclaredFields(); Field[] declaredFields23 = b.getClass().getDeclaredFields(); StringBuilder xiugaiqian = new StringBuilder(); StringBuffer xiugaihou = new StringBuffer(); for (int i = 0; i < declaredFields2.length; i++) { declaredFields2[i].setAccessible(true); declaredFields23[i].setAccessible(true); if (declaredFields2[i].get(a) != null && declaredFields23[i].get(b) != null) { if (!declaredFields2[i].get(a).equals(declaredFields23[i].get(b))) { xiugaiqian.append(declaredFields2[i].getName() + ":" + declaredFields2[i].get(a)).append(","); xiugaihou.append(declaredFields23[i].getName() + ":" + declaredFields23[i].get(b)).append(","); } } else if (declaredFields2[i].get(a) == null && declaredFields23[i].get(b) != null) { xiugaiqian.append(declaredFields2[i].getName() + ":" + null).append(","); xiugaihou.append(declaredFields23[i].getName() + ":" + declaredFields23[i].get(b)).append(","); } else if (declaredFields2[i].get(a) != null && declaredFields23[i].get(b) == null) { xiugaiqian.append(declaredFields2[i].getName() + ":" + declaredFields2[i].get(a)).append(","); xiugaihou.append(declaredFields23[i].getName() + ":" + null).append(","); } } if (StringUtils.isNoneBlank(xiugaiqian.toString()) && StringUtils.isNoneBlank(xiugaihou.toString())) { map.put(xiugaiqian.toString().substring(0, xiugaiqian.length() - 1), xiugaihou.toString().substring(0, xiugaihou.length() - 1)); } return map; } }
測(cè)試代碼
/** * * Description: * @Author:xieyuxin * @param * @param 設(shè)定文件 * @throws * @return String * @Exception 異常對(duì)象 */ package com.shiyan.test; import java.util.Map; import com.shiyan.utils.object.GetDifference; public class ObjectGetEerro { public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException { Persion p1 = new Persion.Builder().age(12).name("張三").t("2018-10-20").build(); Persion p2 = new Persion.Builder().age(16).name("1111").build(); Map<String, String> user = GetDifference.getUser(p1, p2); for(Map.Entry<String, String> entry : user.entrySet()){ System.out.println("修改前 :" + entry.getKey()); System.out.println("修改后 :" + entry.getValue()); } } }
到此這篇關(guān)于Java如何比較兩個(gè)對(duì)象并獲取不相等字段的文章就介紹到這了,更多相關(guān)Java比較兩個(gè)對(duì)象內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java定時(shí)任務(wù)Timer、TimerTask與ScheduledThreadPoolExecutor詳解
這篇文章主要介紹了Java定時(shí)任務(wù)Timer、TimerTask與ScheduledThreadPoolExecutor詳解, 定時(shí)任務(wù)就是在指定時(shí)間執(zhí)行程序,或周期性執(zhí)行計(jì)劃任務(wù),Java中實(shí)現(xiàn)定時(shí)任務(wù)的方法有很多,本文從從JDK自帶的一些方法來(lái)實(shí)現(xiàn)定時(shí)任務(wù)的需求,需要的朋友可以參考下2024-01-01使用spring.profiles.active來(lái)分區(qū)配置的方法示例
這篇文章主要介紹了使用spring.profiles.active來(lái)分區(qū)配置的方法示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-01-01Java 根據(jù)網(wǎng)絡(luò)URL獲取該網(wǎng)頁(yè)上面所有的img標(biāo)簽并下載圖片
這篇文章主要介紹了Java 根據(jù)網(wǎng)絡(luò)URL獲取該網(wǎng)頁(yè)上面所有的img標(biāo)簽并下載圖片,幫助大家更好的理解和使用Java,感興趣的朋友可以了解下2020-11-11Java實(shí)現(xiàn)刪除排序數(shù)組中重復(fù)元素的方法小結(jié)【三種方法比較】
這篇文章主要介紹了Java實(shí)現(xiàn)刪除排序數(shù)組中重復(fù)元素的方法,結(jié)合實(shí)例形式對(duì)比分析了三種常見的數(shù)組元素刪除算法操作技巧,需要的朋友可以參考下2019-02-02Java實(shí)現(xiàn)PDF導(dǎo)出功能的示例代碼
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)PDF導(dǎo)出功能的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以了解下2023-09-09Java try()語(yǔ)句實(shí)現(xiàn)try-with-resources異常管理機(jī)制操作
這篇文章主要介紹了Java try()語(yǔ)句實(shí)現(xiàn)try-with-resources異常管理機(jī)制操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09