大家好,我是wdm, 针对上一篇一层List的DIFF
https://blog.csdn.net/wudaiming/article/details/131500016?spm=1001.2014.3001.5501
这里我在项目组开发是碰到了一种情况,就是嵌套数组是否相等的比较,项目中我通过重写数组对象的equals方法和set实现了判断是否有改动【包括、增、删、改都属于一种状态返回true,另一种就是啥没改返回false】,写完了我想起上篇文章可以优化用到这。
对于嵌套数组的DIFF算法其实可以在比较器中通过反射获取对象属性的Class类型,如果是List或者Arrays就走数组的判断相等逻辑【可以转换成set去重写数组对象的equals方法,最后set1.equals(set2)来判断】,否则就走Java基本类型的equals。
对于实现,其实只需要改动比较器:
package com.wdm.util;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.Comparator;
import java.util.List;
public class ObjectComparator<T> implements Comparator<T> {
@Override
public int compare(T o1, T o2) {
Field[] fields = o1.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
try {
Object value1 = field.get(o1);
Object value2 = field.get(o2);
if (value1 != null && value2 != null && value1.getClass().isArray() && value2.getClass().isArray()) {
if (!isArraysEqual(value1, value2)) {
return compareArrays(value1, value2);
}
} else if (value1 instanceof List && value2 instanceof List) {
if (!isListEqual((List<?>) value1, (List<?>) value2)) {
return compareLists((List<?>) value1, (List<?>) value2);
}
} else if (!isEqual(value1, value2)) {
return compareValues(value1, value2);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return 0;
}
private boolean isArraysEqual(Object arr1, Object arr2) {
int length1 = Array.getLength(arr1);
int length2 = Array.getLength(arr2);
if (length1 != length2) {
return false;
}
for (int i = 0; i < length1; i++) {
Object element1 = Array.get(arr1, i);
Object element2 = Array.get(arr2, i);
if (!isEqual(element1, element2)) {
return false;
}
}
return true;
}
private boolean isListEqual(List<?> list1, List<?> list2) {
int size1 = list1.size();
int size2 = list2.size();
if (size1 != size2) {
return false;
}
for (int i = 0; i < size1; i++) {
Object element1 = list1.get(i);
Object element2 = list2.get(i);
if (!isEqual(element1, element2)) {
return false;
}
}
return true;
}
private boolean isEqual(Object obj1, Object obj2) {
if (obj1 == null && obj2 == null) {
return true;
}
if (obj1 == null || obj2 == null) {
return false;
}
return obj1.equals(obj2);
}
private int compareArrays(Object arr1, Object arr2) {
int length1 = Array.getLength(arr1);
int length2 = Array.getLength(arr2);
int minSize = Math.min(length1, length2);
for (int i = 0; i < minSize; i++) {
Object element1 = Array.get(arr1, i);
Object element2 = Array.get(arr2, i);
if (!isEqual(element1, element2)) {
return compareValues(element1, element2);
}
}
return Integer.compare(length1, length2);
}
private int compareLists(List<?> list1, List<?> list2) {
int size1 = list1.size();
int size2 = list2.size();
int minSize = Math.min(size1, size2);
for (int i = 0; i < minSize; i++) {
Object element1 = list1.get(i);
Object element2 = list2.get(i);
if (!isEqual(element1, element2)) {
return compareValues(element1, element2);
}
}
return Integer.compare(size1, size2);
}
private int compareValues(Object value1, Object value2) {
if (value1 instanceof Comparable && value2 instanceof Comparable) {
return ((Comparable) value1).compareTo(value2);
}
return 0;
}
}
然后对于DiffUtil在上篇文章的错误进行修复后的代码如下:
package com.wdm.util;
import cn.hutool.core.collection.CollUtil;
import com.wdm.enumm.EnumType;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.experimental.Accessors;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.sql.Wrapper;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @author: wudaiming
* @date: 2023/6/29
* @version: 1.0
* @description:
*/
public class DiffListUtil {
@Data
public static class TargetWrapper<T> {
private T target;
private EnumType type;
public TargetWrapper(T target, EnumType type) {
this.target = target;
this.type = type;
}
// Getters and setters for target and type
}
@Data
@Accessors(chain = true)
public static class DiffResult<T> {
/**
* 新增对象列表
*/
private List<TargetWrapper<T>> addedList;
/**
* 修改后的对象列表
*/
private List<TargetWrapper<T>> changedList;
/**
* 已删除对象列表
*/
private List<TargetWrapper<T>> deletedList;
}
/**
* 对比两个List的元素
*
* 如果 baseList 的元素在 targetList 中存在 PrimaryKey 相等的元素并且 elementComparator 比较结果不相等,则将修改后的值添加到changedList列表中;
* 如果 baseList 的元素在 targetList 中不存在,将baseList中的元素添加到deletedList中;
* 如果 targetList 的元素在 baseList 中不存在,将targetList中的元素添加到addedList中;
*
* complexity: O(n)
*
* @param baseList 基础List(原来的List)
* @param targetList 目标List(最新的List)
* @param elementComparator 元素比较器
*primaryKeyExtractor
* @param
* @return 对比结果
*/
public static <T> DiffResult<T> diffList(List<T> baseList,
List<T> targetList,
@NotNull Function<T, Object> primaryKeyExtractor,
@NotNull ObjectComparator<T> elementComparator) {
DiffResult<T> checkResult = checkEmptyAndReturn(baseList, targetList);
if (checkResult != null) {
return checkResult;
}
Map<Object,T> baseMap = new HashMap<>(4096);
for(T base : baseList){
Object key = primaryKeyExtractor.apply(base);
baseMap.put(key,base);
}
List<TargetWrapper<T>> addedList = new ArrayList<>();
List<TargetWrapper<T>> changedList = new ArrayList<>();
List<TargetWrapper<T>> deletedList = new ArrayList<>();
//找出新增的 和需要更新的
for (T target : targetList) {
Object key = primaryKeyExtractor.apply(target);
T base = baseMap.get(key);
if(base == null){
addedList.add(new TargetWrapper<T>(target, EnumType.ADD));
}else{
baseMap.remove(key);
if (elementComparator.compare(base, target) != 0) {
changedList.add(new TargetWrapper<T>(target, EnumType.MODIFIED));
}
}
}
//剩余的就是需要删除的
Set<Map.Entry<Object, T>> entrySet = baseMap.entrySet();
if(CollUtil.isNotEmpty(entrySet)){
for(Map.Entry<Object, T> entry:entrySet){
deletedList.add(new TargetWrapper<T>(entry.getValue(), EnumType.DELETED));
}
}
return new DiffResult<T>()
.setAddedList(addedList)
.setChangedList(changedList)
.setDeletedList(deletedList);
}
private static <T, V> void setFieldValue(T object, Function<? super T,V> fieldGetter, String value) {
try {
Field field = fieldGetter.getClass().getDeclaredField("value");
field.setAccessible(true);
field.set(fieldGetter.apply(object), value);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
/**
* 检查baseList 和 targetList 为empty(null||size==0)的情况
*
* @param baseList
* @param targetList
* @param
* @return
*/
private static <T> DiffResult<T> checkEmptyAndReturn(List<T> baseList, List<T> targetList) {
if (CollUtil.isEmpty(baseList) && CollUtil.isEmpty(targetList)) {
return new DiffResult<T>()
.setAddedList(null)
.setChangedList(null)
.setDeletedList(null);
}
if (CollUtil.isEmpty(baseList) && CollUtil.isNotEmpty(targetList)) {
List<TargetWrapper<T>> wrapperTargetList =
targetList.stream().map(t -> new TargetWrapper<>(t, EnumType.ADD)).collect(Collectors.toList());
return new DiffResult<T>()
.setAddedList(wrapperTargetList)
.setChangedList(null)
.setDeletedList(null);
}
if (CollUtil.isNotEmpty(baseList) && CollUtil.isEmpty(targetList)) {
List<TargetWrapper<T>> wrapperBaseList = baseList.stream().map(t -> new TargetWrapper<>(t, EnumType.DELETED)).collect(Collectors.toList());
return new DiffResult<T>()
.setAddedList(null)
.setChangedList(null)
.setDeletedList(wrapperBaseList);
}
return null;
}
@Data
@AllArgsConstructor
public static class User {
private Integer id;
private String userName;
private String address;
private String email;
private List<User> sonUserList;
}
@Data
@AllArgsConstructor
public static class SonUser {
private Integer id;
private String userName;
private String address;
private String email;
}
}
package com.wdm;
import com.wdm.util.DiffListUtil;
import com.wdm.util.ObjectComparator;
import org.apache.commons.collections.CollectionUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
/**
* @author: wudaiming
* @date: 2023/6/29
* @version: 1.0
* @description:
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class DiffListUtilApplicationTest {
@Test
public void test1() {
List<DiffListUtil.User> userList = new ArrayList<>();
DiffListUtil diffListUtil = new DiffListUtil();
DiffListUtil.User sonUser = new DiffListUtil.User(55, "John", "hunan", "[email protected]", new ArrayList<>());
DiffListUtil.User sonUser11 = new DiffListUtil.User(11, "John", "hunan", "[email protected]", new ArrayList<>());
DiffListUtil.User sonUser12 = new DiffListUtil.User(11, "John1", "hunan", "[email protected]", new ArrayList<>());
userList.add(new DiffListUtil.User(11,"John","hunan","[email protected]", new ArrayList<>(){{
add(sonUser);
//add(sonUser11);
}}));
userList.add(new DiffListUtil.User(22,"Tom","jilin","[email protected]", new ArrayList<>(){{
add(sonUser);
add(sonUser11);
}}));
List<DiffListUtil.User> userListAfter = new ArrayList<>();
userListAfter.add(new DiffListUtil.User(11,"John","hunan","[email protected]", new ArrayList<>(){{
add(sonUser);
//add(sonUser11);
}}));
userListAfter.add(new DiffListUtil.User(22,"Tom","jilin","[email protected]", new ArrayList<>(){{
add(sonUser);
//add(sonUser11);
}}));
Function<DiffListUtil.User, Object> primaryKeyExtractor = user -> user.getId();
ObjectComparator<DiffListUtil.User> userComparator = new ObjectComparator<>();
DiffListUtil.DiffResult<DiffListUtil.User> userDiffResult = diffListUtil.diffList(userListAfter, userList, primaryKeyExtractor, userComparator);
//unionSubIntersection(userList, userListAfter);
System.out.println(userDiffResult);
}
private void unionSubIntersection(List<DiffListUtil.User> userList, List<DiffListUtil.User> userListAfter) {
/**
* @description 求两个集合的不同元素
* @author wudaiming
* @date 13:29 2023/7/20
*/
List<DiffListUtil.User> union = (List<DiffListUtil.User>) CollectionUtils.union(userListAfter, userList);
List<DiffListUtil.User> intersection = (List<DiffListUtil.User>) CollectionUtils.intersection(userListAfter, userList);
List<DiffListUtil.User> difference = findDifference(union, intersection);
}
public List<DiffListUtil.User> findDifference(List<DiffListUtil.User> list1, List<DiffListUtil.User> list2) {
return (List<DiffListUtil.User>) CollectionUtils.subtract(list1, list2);
}
}
最后,其实这个实现我还是只以第一层为判断ADD,MODIFIED,DELETED的主体,也就是说对于嵌套数组中如果对象元素有任何增、删、改都看作第一层的MODIFIED,如果有朋友想要精确到嵌套数组的ADD,MODIFIED,DELETED可以留言讨论!!这个目前对我够用了。
感谢大家看完我的文章,如果大家喜欢我会继续更新项目中遇到的小东西,搞技术就是要不断积累,大家共勉!!!!!