如何用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逻辑与上述一致...
当前代码的问题
- 性能低下:内层循环每次都要遍历
dinner的食材列表并重新收集为List,时间复杂度为O(n*m),列表较大时性能损耗明显 - 代码冗余:Food和Drink的实现逻辑完全重复,没有复用性
- 副作用风险:直接修改传入的
dinner对象的列表,可能引发调用方意料之外的状态变更 - 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
相关产品推荐
相关产品推荐

