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

如何使用Stream API的collect()与Collectors.groupingBy()实现按Ingredient对Product列表分组

如何用Stream API按Ingredient分组Product列表?

你遇到的核心问题是:每个Product对应多个Ingredient,而Collectors.groupingBy()默认是给每个流元素分配一个分组键,但我们需要把一个Product关联到它的所有Ingredient上。直接用groupingBy(Product::getIngredients)肯定不行,因为这样键会是整个Ingredient列表,而不是单个Ingredient。

解决思路是先通过flatMap把每个Product拆分成多个「Ingredient + Product」的配对,然后再按Ingredient分组。下面是完整的实现代码:

import java.util.AbstractMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

// 你的Product类定义
public class Product {
    private String id;
    private List<Ingredient> ingredients;

    public Product(String id, List<Ingredient> ingredients) {
        this.id = id;
        this.ingredients = ingredients;
    }

    public List<Ingredient> getIngredients() {
        return ingredients;
    }

    // 重写toString方便查看结果
    @Override
    public String toString() {
        return "Product{" + "id='" + id + '\'' + '}';
    }
}

// 你的Ingredient类定义(必须重写equals和hashCode)
public class Ingredient {
    private String name;

    public Ingredient(String name) {
        this.name = name;
    }

    // 必须重写!否则相同name的Ingredient会被当成不同的键
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Ingredient that = (Ingredient) o;
        return name.equals(that.name);
    }

    @Override
    public int hashCode() {
        return name.hashCode();
    }

    // 重写toString方便查看结果
    @Override
    public String toString() {
        return "Ingredient{" + "name='" + name + '\'' + '}';
    }
}

// 测试代码
public class Main {
    public static void main(String[] args) {
        Product p1 = new Product("1", List.of(new Ingredient("i1"), new Ingredient("i2"), new Ingredient("i3")));
        Product p2 = new Product("2", List.of(new Ingredient("i1"), new Ingredient("i3"), new Ingredient("i5")));
        Product p3 = new Product("3", List.of(new Ingredient("i2"), new Ingredient("i4"), new Ingredient("i5")));
        List<Product> products = List.of(p1, p2, p3);

        Map<Ingredient, List<Product>> result = products.stream()
                .flatMap(product -> product.getIngredients().stream()
                        .map(ingredient -> new AbstractMap.SimpleEntry<>(ingredient, product)))
                .collect(Collectors.groupingBy(
                        Map.Entry::getKey,
                        Collectors.mapping(Map.Entry::getValue, Collectors.toList())
                ));

        // 打印结果验证
        result.forEach((ingredient, productList) ->
                System.out.println(ingredient + " : " + productList));
    }
}

代码细节解释:

  1. flatMap阶段:把每个Product转换成多个Map.Entry<Ingredient, Product>对象,每个Entry代表这个Product和它的一个Ingredient的关联。flatMap会把所有这些Entry合并成一个流,这样每个Ingredient都能和对应的Product一一对应。
  2. collect阶段:
    • 第一个参数Map.Entry::getKey指定用Ingredient作为分组的键;
    • 第二个参数是下游收集器Collectors.mapping(...),用来把每个Entry中的Product提取出来,最终收集成List。

关键注意事项:

一定要给Ingredient类重写equals()和hashCode()方法!因为groupingBy默认使用HashMap来存储分组结果,而HashMap是通过这两个方法判断两个键是否相等的。如果不重写,即使两个Ingredient的name相同,也会被当成不同的键,导致分组结果不符合预期。

如果你使用的是Java 16及以上版本,可以用Record来替代AbstractMap.SimpleEntry,让代码更简洁易读:

// 定义一个Record来存储配对
record IngredientProductPair(Ingredient ingredient, Product product) {}

// 对应的stream代码
Map<Ingredient, List<Product>> result = products.stream()
        .flatMap(product -> product.getIngredients().stream()
                .map(ingredient -> new IngredientProductPair(ingredient, product)))
        .collect(Collectors.groupingBy(
                IngredientProductPair::ingredient,
                Collectors.mapping(IngredientProductPair::product, Collectors.toList())
        ));

运行测试代码后,你会得到和预期完全一致的结果:

Ingredient{name='i1'} : [Product{id='1'}, Product{id='2'}]
Ingredient{name='i2'} : [Product{id='1'}, Product{id='3'}]
Ingredient{name='i3'} : [Product{id='1'}, Product{id='2'}]
Ingredient{name='i4'} : [Product{id='3'}]
Ingredient{name='i5'} : [Product{id='2'}, Product{id='3'}]

内容的提问来源于stack exchange,提问作者seyda özdemir

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.27 19:17:41