Java中如何获取两个列表的双向差异元素?
Java双向列表差异的实现优化
需求说明
需要对比两个Java列表的双向元素差异:支持列表大小不等或相等的场景,能同时找出仅存在于第一个列表(订单)的元素和仅存在于第二个列表(发货)的元素。现有实现已覆盖所有场景,但希望得到最优实现和优化建议。
现有实现代码
import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { // Case 1 List<String> order1 = Arrays.asList("A", "B"); List<String> sended1 = Arrays.asList("A", "B"); System.out.println("Case 1: " + getDifference(order1, sended1)); // Case 2 List<String> order2 = Arrays.asList("A", "B", "C"); List<String> sended2 = Arrays.asList("B", "C"); System.out.println("Case 2: " + getDifference(order2, sended2)); // Case 3 List<String> order3 = Arrays.asList("A", "D"); List<String> sended3 = Arrays.asList("A", "B", "C", "D"); System.out.println("Case 3: " + getDifference(order3, sended3)); // Case 4 List<String> order4 = Arrays.asList("A", "D", "F"); List<String> sended4 = Arrays.asList("A", "B", "C", "D"); System.out.println("Case 4: " + getDifference(order4, sended4)); } private static String getDifference(List<String> order, List<String> sended) { StringBuilder output = new StringBuilder(); if (order.equals(sended)) { output.append("No difference"); } else { List<String> auxOrder = new ArrayList<>(order); auxOrder.removeAll(sended); if (!auxOrder.isEmpty()) { output.append(auxOrder.size()).append(" item(s) missing in sended: "); output.append("\"").append(String.join("\", \"", auxOrder)).append("\""); } List<String> auxSended = new ArrayList<>(sended); auxSended.removeAll(order); if (!auxSended.isEmpty()) { if (output.length() > 0) { output.append(" & "); } output.append(auxSended.size()).append(" item(s) missing in order: "); output.append("\"").append(String.join("\", \"", auxSended)).append("\""); } } return output.toString(); } }
现有实现的优缺点
优点
- 逻辑清晰,覆盖所有双向差异场景
- 代码简洁,易于理解
- 直接返回格式化字符串,适配打印输出需求
缺点
- 性能瓶颈:
removeAll底层依赖List的contains方法(O(n)复杂度),整体时间复杂度为O(n*m),数据量大时性能明显下降 - 灵活性不足:直接返回字符串,无法对差异数据进行后续业务处理(如统计、批量操作)
- 格式细节:
item(s)写法不够严谨,单复数可根据数量优化
优化方案
1. 用Set优化性能
将列表转换为HashSet,利用Set的O(1)查找特性,将时间复杂度降至O(n+m):
import java.util.HashSet; import java.util.List; import java.util.stream.Collectors; private static String getDifferenceOptimized(List<String> order, List<String> sended) { StringBuilder output = new StringBuilder(); var orderSet = new HashSet<>(order); var sendedSet = new HashSet<>(sended); // 提取仅存在于订单的元素 List<String> onlyInOrder = order.stream() .filter(item -> !sendedSet.contains(item)) .distinct() // 处理列表重复元素 .collect(Collectors.toList()); // 提取仅存在于发货的元素 List<String> onlyInSended = sended.stream() .filter(item -> !orderSet.contains(item)) .distinct() .collect(Collectors.toList()); if (onlyInOrder.isEmpty() && onlyInSended.isEmpty()) { return "No difference"; } // 拼接订单侧差异信息 if (!onlyInOrder.isEmpty()) { String itemStr = onlyInOrder.size() == 1 ? "item" : "items"; output.append(onlyInOrder.size()).append(" ").append(itemStr).append(" missing in sended: "); output.append("\"").append(String.join("\", \"", onlyInOrder)).append("\""); } // 拼接发货侧差异信息 if (!onlyInSended.isEmpty()) { if (output.length() > 0) { output.append(" & "); } String itemStr = onlyInSended.size() == 1 ? "item" : "items"; output.append(onlyInSended.size()).append(" ").append(itemStr).append(" missing in order: "); output.append("\"").append(String.join("\", \"", onlyInSended)).append("\""); } return output.toString(); }
- 新增
distinct()处理列表重复元素场景 - 优化单复数显示逻辑,更符合语言习惯
2. 封装差异结果为自定义对象
若需后续业务处理差异数据,建议返回自定义对象而非字符串:
import java.util.HashSet; import java.util.List; import java.util.stream.Collectors; // 自定义差异结果类(Java 16+) record ListDifference<T>(List<T> onlyInFirst, List<T> onlyInSecond) { public boolean hasDifference() { return !onlyInFirst.isEmpty() || !onlyInSecond.isEmpty(); } // 格式化输出方法 public String format() { if (!hasDifference()) { return "No difference"; } StringBuilder sb = new StringBuilder(); if (!onlyInFirst.isEmpty()) { String itemStr = onlyInFirst.size() == 1 ? "item" : "items"; sb.append(onlyInFirst.size()).append(" ").append(itemStr).append(" missing in sended: "); sb.append("\"").append(String.join("\", \"", (List<String>) onlyInFirst)).append("\""); } if (!onlyInSecond.isEmpty()) { if (sb.length() > 0) sb.append(" & "); String itemStr = onlyInSecond.size() == 1 ? "item" : "items"; sb.append(onlyInSecond.size()).append(" ").append(itemStr).append(" missing in order: "); sb.append("\"").append(String.join("\", \"", (List<String>) onlyInSecond)).append("\""); } return sb.toString(); } } // 获取差异数据的方法 private static ListDifference<String> getListDifference(List<String> order, List<String> sended) { var orderSet = new HashSet<>(order); var sendedSet = new HashSet<>(sended); List<String> onlyInOrder = order.stream() .filter(item -> !sendedSet.contains(item)) .distinct() .collect(Collectors.toList()); List<String> onlyInSended = sended.stream() .filter(item -> !orderSet.contains(item)) .distinct() .collect(Collectors.toList()); return new ListDifference<>(onlyInOrder, onlyInSended); }
- 使用Java 16+的
record简化对象定义 - 分离数据获取与格式化逻辑,提升代码复用性
- 方便后续对差异数据进行统计、导出等操作
3. 处理重复元素数量差异
若需关注元素出现次数的差异(如订单含2个A,发货仅含1个A),可使用Map计数:
import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; private static String getDifferenceWithCount(List<String> order, List<String> sended) { // 统计元素出现次数 Map<String, Integer> orderCount = order.stream() .collect(Collectors.toMap(Function.identity(), v -> 1, Integer::sum)); Map<String, Integer> sendedCount = sended.stream() .collect(Collectors.toMap(Function.identity(), v -> 1, Integer::sum)); StringBuilder output = new StringBuilder(); // 检查订单中数量不足的元素 for (Map.Entry<String, Integer> entry : orderCount.entrySet()) { String item = entry.getKey(); int orderNum = entry.getValue(); int sendedNum = sendedCount.getOrDefault(item, 0); if (sendedNum < orderNum) { int diff = orderNum - sendedNum; String itemStr = diff == 1 ? "item" : "items"; output.append(diff).append(" ").append(itemStr).append(" of ").append(item).append(" missing in sended; "); } } // 检查发货中多出来的元素 for (Map.Entry<String, Integer> entry : sendedCount.entrySet()) { String item = entry.getKey(); int sendedNum = entry.getValue(); int orderNum = orderCount.getOrDefault(item, 0); if (orderNum < sendedNum) { int diff = sendedNum - orderNum; String itemStr = diff == 1 ? "item" : "items"; output.append(diff).append(" ").append(itemStr).append(" of ").append(item).append(" missing in order; "); } } return output.isEmpty() ? "No difference" : output.substring(0, output.length() - 2); }
- 适用于需要精确统计元素数量差异的业务场景
总结
- 数据量小、仅需简单对比时,原有实现完全可用
- 数据量较大时,优先使用Set优化方案提升性能
- 需后续业务处理差异数据时,推荐自定义差异对象方案
- 涉及重复元素数量对比时,选择Map计数方案
内容的提问来源于stack exchange,提问作者Diego Borba
相关产品推荐
相关产品推荐

