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

如何用Java Stream高效获取两个Meal中独有的Food和Drink?

优化餐食独特食材/饮品集合的实现方案

背景

给定以下POJO类,所有属性可为null且未用Optional包装:

public class Food{
    String name;    // 可为null
    Double weight;
    // getters & setters & constructor
}
public class Drink{
    String name;    // 可为null
    Long volume;
    // getters & setters & constructor
}
public class Meal{
    Long id;
    Timestamp date;
    List<Food> foodList;
    List<Drink> drinkList;
    // getters & setters & constructor
}

现有两个已完成属性赋值的Meal实例lunch和dinner,需要实现:

List<Food> uniqueFoodInLunchAndDinner = ....    // 获取午餐和晚餐中所有不重复的Food
List<Drink> uniqueDrinkInLunchAndDinner = ....  // 获取午餐和晚餐中所有不重复的Drink

当前实现代码存在性能和冗余问题,现寻求更简洁且不牺牲性能的方案。当前代码如下:

List<Food> getUniqueFoodInLunchAndDinner(Meal lunch, Meal dinner){
    List<Food> missingFoodinDinner = lunch.getFood().stream().filter(Objects::nonNull).filter(foodInLunch -> {
        return dinner.getFood().stream()
                        .filter(Objects::nonNull)
                        .map(Food::getName)
                        .filter(Objects::nonNull)
                        .collect(Collectors.toList())
                        .contains(foodInLunch.getName()) == false;
    }).collect(Collectors.toList());
    dinner.getFood().addAll(missingFoodinDinner);
    return dinner.getFood();
}
// Drink逻辑与上述一致...

当前代码的问题

  1. 性能低下:内层循环每次都要遍历dinner的食材列表并重新收集为List,时间复杂度为O(n*m),列表较大时性能损耗明显
  2. 代码冗余:Food和Drink的实现逻辑完全重复,没有复用性
  3. 副作用风险:直接修改传入的dinner对象的列表,可能引发调用方意料之外的状态变更
  4. null处理不严谨:未考虑foodList本身为null的情况,可能触发空指针异常

优化方案

核心思路

  • 用HashSet存储已存在的名称,将查找操作的时间复杂度从O(m)降至O(1)
  • 抽取通用逻辑,通过泛型和函数式接口复用代码
  • 返回新集合,避免修改原对象的状态
  • 完善null安全处理

实现代码

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

public class MealProcessor {

    // 通用方法:合并两个列表,返回去重后的元素集合(按指定字段去重,忽略null元素和null字段)
    private static <T> List<T> getUniqueItems(List<T> listA, List<T> listB, Function<T, String> identifierExtractor) {
        // 先收集列表B中有效元素的标识到Set,用于快速查找
        Set<String> existingIdentifiers = Optional.ofNullable(listB)
                .orElse(Collections.emptyList())
                .stream()
                .filter(Objects::nonNull)
                .map(identifierExtractor)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());

        // 初始化结果集:先加入列表B的所有有效元素
        List<T> result = new ArrayList<>(Optional.ofNullable(listB)
                .orElse(Collections.emptyList())
                .stream()
                .filter(Objects::nonNull)
                .collect(Collectors.toList()));

        // 加入列表A中不在集合B里的有效元素
        Optional.ofNullable(listA)
                .orElse(Collections.emptyList())
                .stream()
                .filter(Objects::nonNull)
                .filter(item -> {
                    String id = identifierExtractor.apply(item);
                    return id != null && !existingIdentifiers.contains(id);
                })
                .forEach(result::add);

        return result;
    }

    // 获取午餐和晚餐中所有不重复的Food
    public static List<Food> getUniqueFood(Meal lunch, Meal dinner) {
        return getUniqueItems(lunch.getFoodList(), dinner.getFoodList(), Food::getName);
    }

    // 获取午餐和晚餐中所有不重复的Drink
    public static List<Drink> getUniqueDrink(Meal lunch, Meal dinner) {
        return getUniqueItems(lunch.getDrinkList(), dinner.getDrinkList(), Drink::getName);
    }
}

优化点说明

  • 性能提升:整体时间复杂度降至O(n+m),大列表场景下性能显著提升
  • 代码复用:通过泛型抽象通用逻辑,Food和Drink的实现仅需一行代码,避免冗余
  • 无副作用:返回新的ArrayList实例,不会修改传入的lunch或dinner对象的原有列表
  • null安全:用Optional.ofNullable处理可能为null的列表,全程避免空指针异常
  • 逻辑清晰:拆分步骤,代码可读性更强

额外说明

如果业务场景需要保留第一个出现的元素(比如优先保留晚餐的食材,再补充午餐中没有的),当前方案完全符合需求。如果需要去重时合并属性(比如合并相同name的Food的weight),则需要调整逻辑,在遇到重复name时进行属性合并而非直接跳过。

内容的提问来源于stack exchange,提问作者Dreamer

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 14:15:35