You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

现有实现的优缺点

优点

  • 逻辑清晰,覆盖所有双向差异场景
  • 代码简洁,易于理解
  • 直接返回格式化字符串,适配打印输出需求

缺点

  1. 性能瓶颈:removeAll底层依赖List的contains方法(O(n)复杂度),整体时间复杂度为O(n*m),数据量大时性能明显下降
  2. 灵活性不足:直接返回字符串,无法对差异数据进行后续业务处理(如统计、批量操作)
  3. 格式细节: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 20:50:54