Java 优雅地 Diff 两个 List 来获取新增、修改和删除的数据
场景描述
有时候会将数据库中查出的一组数据返回给前端页面进行修改:用户通过界面可以删除一些数据,也可以增加一些数据并修改一些数据,然后将这些修改后的数据重新保存到数据库中。这样说可能有些抽象,可以看下面这张图来更直观的理解。
这张表的左侧是经过用户修改后传递到服务端待更新的数据,右侧是当前数据库中查出的数据。假设数据的唯标识为 id,通过比较可以看出,id 为 2 和 3 的元素左右两侧都有,因此这两条数据是被修改的元素。左侧有一个 id 为空的数据,因此是新增的数据。右侧有一个 id 为 4 的数据,左侧没有,因此是这是被用户删除的数据。
因此我们要执行的数据库操作如下:
- 删除 id 为 4 的数据;
- 修改 id 为 2 和 3 的数据;
- 新增一条数据,并给 id 赋值。
执行这个数据库操作并不难,但是在这之前我们得 Diff 前端页面传递过来的 specDTOList 和 数据库中查出的 specList。Diff 的结果是三个 List,分别用来存储新增、修改和删除的元素。因此我们需要定义一个类来封装 Diff 的结果。
public static class DiffR<T, E> {
private final List<T> newList;
private final List<T> modList;
private final List<E> delList;
public DiffR(List<T> newList, List<T> modList, List<E> delList) {
this.newList = newList;
this.modList = modList;
this.delList = delList;
}
public List<T> getN() {
return newList;
}
public List<T> getM() {
return modList;
}
public List<E> getD() {
return delList;
}
}
通过上面的泛型定义可以看出我的用意:我的目标是 Diff 任意两个 List,而不局限于特定类型的两个 List。那么该怎样定义 diff 函数呢?
public static <T, E> DiffR<T, E> diff(List<T> lst1, List<E> lst2);
这样可以吗?显然不可以,因为我们还需要知道根据什么字段来 diff lst1 和 lst2。因此,除了两个待比较的 list 之外,我们还需要明确给出这两个 List 中的元素要根据什么字段来 diff。这需要一点技巧:
public static <T, E, R> DiffR<T, E> diff(List<T> lst1, List<E> lst2, Function<T, R> f1, Function<E, R> f2);
对比之前的函数参数,又多了两个 Function 类型的参数。为什么是 Function?Function<X, Y>
可以看成是 X 到 Y 的映射。拿 f1 来说,就是将 T 映射到 R,通过该函数我们可以将元素映射到字段,这不就达到了指定字段的目的了吗?同理 f2 是用来指定 lst2 中的元素到字段的映射。
有了上面的铺垫,下面让我们来实现这个 diff 函数。
Diff 方法实现
public static <T, E, R> DiffR<T, E> diff(List<T> lst1, List<E> lst2, Function<T, R> f1, Function<E, R> f2) {
List<T> newList = new ArrayList<>();
List<T> modList = new ArrayList<>();
List<E> delList = new ArrayList<>();
for (T t : lst1) {
if (f1.apply(t) == null) {
newList.add(t);
}
}
Map<R, E> lst2Map = lst2.stream().collect(Collectors.toMap(f2, e -> e, (a, b) -> b));
for (T t : lst1) {
R k1 = f1.apply(t);
if (k1 != null && lst2Map.containsKey(k1)) {
modList.add(t);
}
}
Map<R, T> lst1Map = lst1.stream().collect(Collectors.toMap(f1, e -> e, (a, b) -> b));
for (E t : lst2) {
R k2 = f2.apply(t);
if (k2 != null && !lst1Map.containsKey(k2)) {
delList.add(t);
}
}
return new DiffR<>(newList, modList, delList);
}
Diff 方法使用实战
假设 Spec 和 SpecDTO 定义如下:
@Data
public class Spec {
private String id;
private String name;
}
@Data
public class SpecDTO {
private String id;
private String name;
}
现在我们要比较 specDTOList 和 specList,使用方法如下:
Diff<SpecDTO, Spec> r = diff(specDTOList, specList, SpecDTO::getId, Spec::getId);
r.getNewList();
r.getModList();
r.getDelList();
完整代码可参考 CollectionUtil.java。
温馨提示:反馈需要登录