如何用Java Stream reduce()按需合并或更新集合元素
问题:按Category合并Info对象的orderIds与price
现有一个List<Info>集合,其中每个Info对象的orderIds列表仅包含一个元素。需要基于此生成Set<Info>或List<Info>,当两个Info对象的category相同时,不新增元素,而是合并orderIds并累加price。
代码定义
class Info { String category; List<Integer> orderIds; int price; public Info(String category, List<Integer> orderIds, int price) { this.category = category; this.orderIds = orderIds; this.price = price; } }
示例数据
private static List<Info> getInfos() { List<Info> infos = new ArrayList<>(); infos.add(new Info("CATEGORY_1", Collections.singletonList(110), 200)); infos.add(new Info("CATEGORY_1", Collections.singletonList(120), 300)); infos.add(new Info("CATEGORY_2", Collections.singletonList(210), 1000)); infos.add(new Info("CATEGORY_2", Collections.singletonList(220), 2000)); infos.add(new Info("CATEGORY_3", Collections.singletonList(300), 400)); return infos; }
期望结果
Info("CATEGORY_1", List(110,120), 500); Info("CATEGORY_2", List(210,220), 3000); Info("CATEGORY_3", List(300), 400);
现有代码框架
getInfos().stream() .reduce(new HashSet<Info>(), ?? , ?? );
解决方案
步骤1:完善Info类
要实现按category判断对象是否重复,需要重写equals和hashCode方法;同时为了避免修改原对象的不可变集合,合并时需创建新列表:
import java.util.Objects; import java.util.List; import java.util.stream.Collectors; class Info { String category; List<Integer> orderIds; int price; public Info(String category, List<Integer> orderIds, int price) { this.category = category; this.orderIds = orderIds; this.price = price; } // 基于category判断对象相等 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Info info = (Info) o; return Objects.equals(category, info.category); } @Override public int hashCode() { return Objects.hash(category); } // 重写toString方便查看结果 @Override public String toString() { return "Info(\"" + category + "\", List(" + orderIds.stream().map(String::valueOf).collect(Collectors.joining(",")) + "), " + price + ")"; } }
步骤2:用reduce实现合并逻辑
这里使用ArrayList作为累加器(比HashSet更直观,便于处理合并逻辑),实现累加器和组合器的具体逻辑:
import java.util.ArrayList; import java.util.Collections; import java.util.List; public class Main { private static List<Info> getInfos() { List<Info> infos = new ArrayList<>(); infos.add(new Info("CATEGORY_1", Collections.singletonList(110), 200)); infos.add(new Info("CATEGORY_1", Collections.singletonList(120), 300)); infos.add(new Info("CATEGORY_2", Collections.singletonList(210), 1000)); infos.add(new Info("CATEGORY_2", Collections.singletonList(220), 2000)); infos.add(new Info("CATEGORY_3", Collections.singletonList(300), 400)); return infos; } public static void main(String[] args) { List<Info> mergedInfos = getInfos().stream() .reduce(new ArrayList<>(), // 累加器:处理单个元素与累加集合的合并 (acc, current) -> { acc.stream() .filter(info -> info.equals(current)) .findFirst() .ifPresentOrElse( existing -> { // 合并orderIds并累加price existing.orderIds.addAll(current.orderIds); existing.price += current.price; }, () -> { // 新增元素时复制orderIds,避免修改原不可变集合 acc.add(new Info(current.category, new ArrayList<>(current.orderIds), current.price)); } ); return acc; }, // 组合器:并行流场景下合并两个中间结果集合 (list1, list2) -> { list2.forEach(item -> { list1.stream() .filter(info -> info.equals(item)) .findFirst() .ifPresentOrElse( existing -> { existing.orderIds.addAll(item.orderIds); existing.price += item.price; }, () -> list1.add(item) ); }); return list1; }); // 输出合并后的结果 mergedInfos.forEach(System.out::println); } }
补充:更简洁的Collectors.toMap实现
如果不局限于使用reduce,Collectors.toMap可以更简洁地完成需求:
import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; List<Info> mergedInfos = new ArrayList<>(getInfos().stream() .collect(Collectors.toMap( Info::getCategory, // 以category作为key info -> new Info(info.category, new ArrayList<>(info.orderIds), info.price), // 初始value为新的Info对象 (existing, newInfo) -> { // 重复key时的合并逻辑 existing.orderIds.addAll(newInfo.orderIds); existing.price += newInfo.price; return existing; } )).values());
内容的提问来源于stack exchange,提问作者ThrowableException
相关产品推荐
相关产品推荐

