快速找出两个集合间新增的数据、修改的数据、删除的数据

在日常的开发中,会经常遇到前端传过来的集合(VO 集合)与后端从数据库查出来的集合(DO 集合)做对比,找出新增的,删除的及修改的,然后再做判断或转换等操作。两个集合里面的元素可能类型不一样,也可能是一样的类型,但是只需要比较指定字段(例如用 id 等)是否一样,如果重写hashCodeequals方法不仅显得割裂,代码也臃肿,同时也做不到一次性找出增删改的元素。所以这里自定义一个集合工具类,支持高度自定义和扩展,支持自己指定比较的 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;
  }
}

最后是集合工具类代码

该工具类提供了如下几个方法

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();
  }
}