快速找出两个集合间新增的数据、修改的数据、删除的数据
在日常的开发中,会经常遇到前端传过来的集合(VO 集合)与后端从数据库查出来的集合(DO 集合)做对比,找出新增的,删除的及修改的,然后再做判断或转换等操作。两个集合里面的元素可能类型不一样,也可能是一样的类型,但是只需要比较指定字段(例如用 id 等)是否一样,如果重写
hashCode和equals方法不仅显得割裂,代码也臃肿,同时也做不到一次性找出增删改的元素。所以这里自定义一个集合工具类,支持高度自定义和扩展,支持自己指定比较的 key,支持自定义判断两个 key 值一样的对象是否发生变化,对于发生了变化的元素支持元组方式返回。ps:本来想提交到
Hutool的集合工具类里面去,但是被作者大大给否了。个人认为还是挺有用的小工具方法,可以极大减少比较处的业务代码,所以就将实现代码粘在这里,需要的自取。
首先是二元组代码
ps:如果内部有依赖commons-lang3的包,可以使用它自带的元组类,名字一样。
package com.common.utils;
import java.io.Serializable;
/**
* 不可变二元组对象
*
* @author liuchao
*/
public class Pair<L, R> implements Serializable {
private static final long serialVersionUID = 1L;
/** 构建Pair对象 */
public static <L, R> Pair<L, R> of(final L left, final R right) {
return new Pair<>(left, right);
}
/** 左值 */
protected L left;
/** 右值 */
protected R right;
public Pair(final L left, final R right) {
this.left = left;
this.right = right;
}
/** 获取左值 */
public L getLeft() {
return this.left;
}
/** 获取右值 */
public R getRight() {
return this.right;
}
}
其次是三元组类代码
ps:如果内部有依赖commons-lang3的包,可以使用它自带的元组类,名字一样。
package com.common.utils;
import java.io.Serializable;
/**
* 不可变三元组对象
*
* @author liuchao
*/
public class Triple<L, M, R> implements Serializable {
private static final long serialVersionUID = 1L;
/** 构建Triple对象 */
public static <L, M, R> Triple<L, M, R> of(final L left, final M middle, final R right) {
return new Triple<>(left, middle, right);
}
/** 左值 */
protected L left;
/** 中值 */
protected M middle;
/** 右值 */
protected R right;
public Triple(final L left, final M middle, final R right) {
this.left = left;
this.middle = middle;
this.right = right;
}
/** 获取左值 */
public L getLeft() {
return this.left;
}
/** 获取中值 */
public M getMiddle() {
return this.middle;
}
/** 获取右值 */
public R getRight() {
return this.right;
}
}
最后是集合工具类代码
该工具类提供了如下几个方法
- 提供两类型一样的集合,找出新增的和删除的
- 提供两类型一样的集合,找出新增的、删除的、修改的(修改前后元素组合返回)
- 提供两类型一样的集合,找出新增的、删除的、修改的(修改前后元素自定义融合)
- 提供两类型不一样的集合,找出新增的、删除的、修改的(修改前后元素自定义融合)
- 支持集合自定义 key 去重
- 支持集合自定义 key 去重,重复的元素剔除支持自定义
package com.common.utils;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* @author liuchao
*/
public class ColUtil {
/**
* 对比找出两个{@link Collection}变动,前者(coll1)相对于后者(coll2)的变动,该方法只可提取新增的和删除的元素
*
* @param coll1 原始集合
* @param coll2 对比集合
* @param keyMapper 生成对比键的映射函数
* @param <T> 集合中元素类型
* @param <K> 对比的key值类型
* @return 比较结果,key:coll1对比coll2里面新增的;value:coll1对比coll2里面删除的;
* <pre>{@code
* // 对下面的People集合使用id进行对比,筛选出新增的和删除的
* List<People> coll1 = ListUtil.of(new People(1L, "Tom"), new People(2L, "Tony"));
* List<People> coll2 = ListUtil.of(new People(2L, "Tony2"), new People(null, "Jerry"));
*
* MutableEntry<List<People>, List<People>> pair = ColUtil.diff(coll1, coll2, People::getId);
*
* System.out.println("新增的:" + pair.getLeft());
* System.out.println("删除的:" + pair.getRight());
*
* // 控制台输出
* // 新增的:[People(id=null, name=Jerry)]
* // 删除的:[People(id=1, name=Tom)]
* }</pre>
*
* @see #diff(Collection, Collection, Function, BiPredicate, BiFunction)
* @see #diff(Collection, Collection, Function, Function, BiPredicate, BiFunction)
*/
public static <T, K> Pair<List<T>, List<T>> diff(
Collection<T> coll1, Collection<T> coll2, Function<T, K> keyMapper) {
Triple<List<T>, List<T>, List<Object>> triple =
diff(coll1, coll2, keyMapper, keyMapper, null, null);
return Pair.of(triple.getLeft(), triple.getMiddle());
}
/**
* 对比找出两个{@link Collection}变动,前者(coll1)相对于后者(coll2)的变动,该方法可提取新增、删除和修改的元素
*
* @param coll1 原始集合
* @param coll2 对比集合
* @param keyMapper 生成对比键的映射函数
* @param equalFunction 对比函数,比较键一样的元素是否发生变化
* @param <T> 集合中元素类型
* @param <K> 对比的key值类型
* @return 比较结果,left:coll1对比coll2里面新增的;middle:coll1对比coll2里面删除的;right:coll1对比coll2里面发生改变的
* <pre>{@code
* // 对下面的People集合使用id进行对比,筛选出新增的和删除的
* List<People> coll1 = ListUtil.of(new People(1L, "Tom"), new People(2L, "Tony"));
* List<People> coll2 = ListUtil.of(new People(2L, "Tony2"), new People(null, "Jerry"));
*
* Triple<List<People>, List<People>, List<Pair<People, People>>> triple =
* ColUtil.diff(
* coll1,
* coll2,
* People::getId,
* (p1, p2) -> p1.getName().equals(p2.getName()));
*
* System.out.println("新增的:" + triple.getLeft());
* System.out.println("删除的:" + triple.getMiddle());
* triple.getRight().forEach(pair -> System.out.println("修改前:" + pair.getLeft() + ";修改后:" + pair.getRight()));
*
* // 控制台输出
* // 新增的:[People(id=null, name=Jerry)]
* // 删除的:[People(id=1, name=Tom)]
* // 修改前:ColUtil.People(id=2, name=Tony);修改后:ColUtil.People(id=2, name=Tony2)
* }</pre>
*
* @see #diff(Collection, Collection, Function)
* @see #diff(Collection, Collection, Function, Function, BiPredicate, BiFunction)
*/
public static <T, K> Triple<List<T>, List<T>, List<Pair<T, T>>> diff(
Collection<T> coll1,
Collection<T> coll2,
Function<T, K> keyMapper,
BiPredicate<T, T> equalFunction) {
return diff(coll1, coll2, keyMapper, keyMapper, equalFunction, Pair::of);
}
/**
* 对比找出两个{@link Collection}变动,前者(coll1)相对于后者(coll2)的变动,该方法可提取新增、删除和修改的元素
*
* @param coll1 原始集合
* @param coll2 对比集合
* @param keyMapper 生成对比键的映射函数
* @param equalFunction 对比函数,比较键一样的元素是否发生变化
* @param mergeFunction 元素变动处理选择器,必填参数,apply第一个参数为原始集合元素,第二个参数为对比集合元素
* @param <T> 集合中元素类型
* @param <K> 对比的key值类型
* @param <R> 变更的新旧元素合并后的类型
* @return 比较结果,left:coll1对比coll2里面新增的;middle:coll1对比coll2里面删除的;right:coll1对比coll2里面发生改变的
* <pre>{@code
* // 对下面的People集合使用id进行对比,筛选出新增的和删除的
* List<People> coll1 = ListUtil.of(new People(1L, "Tom"), new People(2L, "Tony"));
* List<People> coll2 = ListUtil.of(new People(2L, "Tony2"), new People(null, "Jerry"));
*
* Triple<List<People>, List<People>, List<People>> triple =
* ColUtil.diff(
* coll1,
* coll2,
* People::getId,
* (p1, p2) -> p1.getName().equals(p2.getName()),
* (p1, p2) -> p2);
*
* System.out.println("新增的:" + triple.getLeft());
* System.out.println("删除的:" + triple.getMiddle());
* System.out.println("修改的:" + triple.getRight());
*
* // 控制台输出
* // 新增的:[People(id=null, name=Jerry)]
* // 删除的:[People(id=1, name=Tom)]
* // 修改的:[People(id=2, name=Tony2)]
* }</pre>
*
* @see #diff(Collection, Collection, Function)
* @see #diff(Collection, Collection, Function, Function, BiPredicate, BiFunction)
*/
public static <T, K, R> Triple<List<T>, List<T>, List<R>> diff(
Collection<T> coll1,
Collection<T> coll2,
Function<T, K> keyMapper,
BiPredicate<T, T> equalFunction,
BiFunction<T, T, R> mergeFunction) {
return diff(coll1, coll2, keyMapper, keyMapper, equalFunction, mergeFunction);
}
/**
* 对比找出两个{@link Collection}变动,前者(coll1)相对于后者(coll2)的变动,该方法可提取新增、删除和修改的元素
*
* @param collForT 原始集合
* @param collForU 对比集合
* @param keyMapperForT 原始集合生成对比键的映射函数
* @param keyMapperForU 对比集合生成对比键的映射函数
* @param equalFunction 对比函数,比较键一样的元素是否发生变化
* @param mergeFunction 元素变动处理选择器,必填参数,apply第一个参数为原始集合元素,第二个参数为对比集合元素
* @param <T> 原始集合(coll1)中元素类型
* @param <U> 对比集合(coll2)中元素类型
* @param <K> 对比的key值类型
* @param <R> 变更的新旧元素合并后的类型
* @return 比较结果,left:coll1对比coll2里面新增的;middle:coll1对比coll2里面删除的;right:coll1对比coll2里面发生改变的
* <pre>{@code
* // 对下面的People集合使用id进行对比,筛选出新增的和删除的
* List<People> coll1 = ListUtil.of(new People(1L, "Tom"), new People(2L, "Tony"));
* List<American> coll2 = ListUtil.of(new American(2L, "Tony2"), new American(null, "Jerry"));
*
* Triple<List<American>, List<People>, List<American>> triple =
* ColUtil.diff(
* coll1,
* coll2,
* People::getId,
* American::getId,
* (p, a) -> p.getName().equals(a.getName()),
* (p, a) -> a);
*
* System.out.println("新增的:" + triple.getLeft());
* System.out.println("删除的:" + triple.getMiddle());
* System.out.println("修改的:" + triple.getRight());
*
* // 控制台输出
* // 新增的:[American(id=null, name=Jerry)]
* // 删除的:[People(id=1, name=Tom)]
* // 修改的:[American(id=2, name=Tony2)]
* }</pre>
*
* @see #diff(Collection, Collection, Function)
* @see #diff(Collection, Collection, Function, BiPredicate, BiFunction)
*/
public static <T, U, K, R> Triple<List<U>, List<T>, List<R>> diff(
Collection<T> collForT,
Collection<U> collForU,
Function<T, K> keyMapperForT,
Function<U, K> keyMapperForU,
BiPredicate<T, U> equalFunction,
BiFunction<T, U, R> mergeFunction) {
Objects.requireNonNull(keyMapperForT);
Objects.requireNonNull(keyMapperForU);
// 记录 collForT 里面新加的元素
List<U> addElementList = new ArrayList<>();
// 记录 collForT 里面删除的元素
List<T> removeElementList = new ArrayList<>();
// 记录 collForT 里面修改的元素
List<R> modifyElementList = new ArrayList<>();
if (isEmpty(collForT)) {
if (!isEmpty(collForU)) {
addElementList.addAll(collForU);
}
return Triple.of(addElementList, removeElementList, modifyElementList);
}
if (isEmpty(collForU)) {
if (!isEmpty(collForT)) {
removeElementList.addAll(collForT);
}
return Triple.of(addElementList, removeElementList, modifyElementList);
}
// 为了保证之前集合的元素顺序,这里使用 LinkedHashMap 和 LinkedHashSet
LinkedHashMap<K, T> mapForT =
collForT.stream()
.collect(Collectors.toMap(keyMapperForT, t -> t, (t, t2) -> t, LinkedHashMap::new));
LinkedHashMap<K, U> mapForU =
collForU.stream()
.collect(Collectors.toMap(keyMapperForU, u -> u, (u, u2) -> u, LinkedHashMap::new));
LinkedHashSet<K> unionKeySet = new LinkedHashSet<>();
unionKeySet.addAll(mapForU.keySet());
unionKeySet.addAll(mapForT.keySet());
for (K k : unionKeySet) {
T t = mapForT.get(k);
U u = mapForU.get(k);
if (Objects.isNull(t)) {
// collForT 里面新加的
addElementList.add(u);
} else if (Objects.isNull(u)) {
// collForT 里面删除的
removeElementList.add(t);
} else {
if (Objects.nonNull(equalFunction)) {
boolean equal = equalFunction.test(t, u);
if (!equal) {
// collForT 里面变动的
R apply = mergeFunction.apply(t, u);
modifyElementList.add(apply);
}
}
}
}
return Triple.of(addElementList, removeElementList, modifyElementList);
}
/**
* 集合支持指定key进行去重
*
* @param coll 需要去重的集合
* @param keyMapper 对比键的映射函数
* @return 去重之后的新集合
* @param <T> 去重集合(coll)中元素类型
* @param <K> 对比的key值类型
*/
public static <T, K> List<T> distinct(Collection<T> coll, Function<T, K> keyMapper) {
if (isEmpty(coll)) {
return new ArrayList<>();
}
LinkedHashMap<K, T> map =
coll.stream()
.collect(Collectors.toMap(keyMapper, t -> t, (t, t2) -> t, LinkedHashMap::new));
return new ArrayList<>(map.values());
}
/**
* 集合支持指定key进行去重,并按指定方式对重复的内容进行合并
*
* @param coll 需要去重的集合
* @param keyMapper 对比键的映射函数
* @param mergeFunction 合并函数
* @return 去重之后的新集合
* @param <T> 去重集合(coll)中元素类型
* @param <K> 对比的key值类型
*/
public static <T, K> List<T> distinct(
Collection<T> coll, Function<T, K> keyMapper, BinaryOperator<T> mergeFunction) {
if (isEmpty(coll)) {
return new ArrayList<>();
}
LinkedHashMap<K, T> map =
coll.stream()
.collect(Collectors.toMap(keyMapper, t -> t, mergeFunction, LinkedHashMap::new));
return new ArrayList<>(map.values());
}
private static boolean isEmpty(Collection<?> collection) {
return Objects.isNull(collection) || collection.isEmpty();
}
}