簡體   English   中英

如何在 Java 8 中在 flatMap() 之上使用收集器 groupingBy() 按屬性對對象進行分組

[英]How to group Objects by property using using collector groupingBy() on top of flatMap() in Java 8

如何創建下面的Map<String,List<Product>> 在這里, StringMap)是Productcategory

一種產品可以屬於多個類別,如下例所示。

我正在嘗試使用以下代碼,但無法進行下一個操作:

products.stream()
    .flatMap(product -> product.getCategories().stream())
    . // how should I progress from here?

結果應如下所示:

{電子=[p1,p3,p4], 時尚=[p1,p2,p4], 廚房=[p1,p2,p3], abc1=[p2], xyz1=[p3],pqr1=[p4]}

Product p1 = new Product(123, Arrays.asList("electonics,fashion,kitchen".split(",")));
Product p2 = new Product(123, Arrays.asList("abc1,fashion,kitchen".split(",")));
Product p3 = new Product(123, Arrays.asList("electonics,xyz1,kitchen".split(",")));
Product p4 = new Product(123, Arrays.asList("electonics,fashion,pqr1".split(",")));
List<Product> products = Arrays.asList(p1, p2, p3, p4);
class Product {

    int price;
    List<String> categories;

    public Product(int price) {
        this.price = price;
    }

    public Product(int price, List<String> categories) {
        this.price = price;
        this.categories = categories;
    }

    public int getPrice() {
        return price;
    }

    public List<String> getCategories() {
        return categories;
    }
}

如果您出於某種原因想使用收集器groupingBy() ,那么您可以定義一個包裝器 class (使用 Java 16+記錄會更方便),它將包含對類別產品的引用來代表每個組合給定列表中存在的類別/產品

public record ProductCategory(String category, Product product) {}

Java 16 之前的替代方案:

public class ProductCategory {
    private String category;
    private Product product;
    
    // constructor and getters
}

然后在利用收集器mapping()toList()的組合作為groupingBy()下游收集器

List<Product> products = // initializing the list of products
        
Map<String, List<Product>> productsByCategory = products.stream()
    .flatMap(product -> product.getCategories().stream()
        .map(category -> new ProductCategory(category, product)))
    .collect(Collectors.groupingBy(
        ProductCategory::category,                   // ProductCategory::getCategory if you used a class instead of record
        Collectors.mapping(ProductCategory::product, // ProductCategory::getProduct if you used a class instead of record
            Collectors.toList())
    ));

在線演示的鏈接


但是,與其創建中間對象和生成嵌套流,不如collect()三參數版本(或定義自定義收集器)中描述累積策略,而不是創建中間對象。

這就是它的實現方式:

Map<String, List<Product>> productsByCategory = products.stream()
    .collect(
        HashMap::new,
        (Map<String, List<Product>> map, Product next) -> next.getCategories()
            .forEach(category -> map.computeIfAbsent(category, k -> new ArrayList<>())
                .add(next)),
        (left, right) -> right.forEach((k, v) -> 
            left.merge(k, v,(oldProd, newProd) -> { oldProd.addAll(newProd); return oldProd; }))
    );

在線演示的鏈接

我嘗試了一些事情並提出了以下解決方案:

Map<Object, List<Product>> result =
        products.stream()
                .flatMap(product -> product.getCategories().stream().map(p -> Map.entry(p, product)))
                .collect(Collectors.groupingBy(Map.Entry::getKey, Collectors.mapping(Map.Entry::getValue, Collectors.toList())));


System.out.println(result);

Output:

xyz1=[org.example.Product@15db9742], electonics=[org.example.Product@6d06d69c, org.example.Product@15db9742, org.example.Product@7852e922], abc1=[org.ex ...

編輯:我已經看到我的解決方案與其他答案非常相似。 但是,我的解決方案使用Map.Entry而不是用戶定義的 object 將數據帶入正確的形狀。

這也可以通過flatMappingtoMap的組合來完成:

Map<String, List<Product>> obj = products.stream()
    .collect(
        Collectors.flatMapping(
            product -> product.categories().stream()
                .map(category -> Map.entry(category, List.of(product))),
            Collectors.toMap(
                Map.Entry::getKey,
                Map.Entry::getValue,
                (v1, v2) -> Stream.concat(v1.stream(), v2.stream()).toList()
            )
    ));

這里發生的情況是,首先,每個Product都轉換為Map.Entry<String, List<Product>> ,其中鍵是類別,值是Product本身,或者更准確地說是List<Product> ,此列表最初僅包含當前產品。

然后,您可以使用toMap “解壓” map 條目。 當然,對於那些 key (=category) 相同的情況,必須合並值(即ListProduct )。


注意:我在這里使用了Map.Entry ,但您也可以編寫自定義 class ,這在語義上更可取(類似於CategoryProductsMapping(String category, List<Product> products)

我知道所有者詢問了 groupby 和 flatmap,但以防萬一,我會提到reduce

  1. 我相信這很簡單; 感覺就像.collect(方法。
  2. 為了使用parallelstream,你必須合並兩個hashmap,我認為這不是一個好主意,還有額外的迭代,我認為在這種情況下它沒有用。
HashMap<String, List<Product>> reduce = products.stream().reduce(
        new HashMap<>(),
        (result, product) -> {
            product.getCategories().stream().distinct().forEach(
                    category -> result.computeIfAbsent(category, k -> new ArrayList<>())
                            .add(product)
            );
            return result;
        },
        (x, y) -> {
            throw new RuntimeException("does not support parallel!");
        }
);

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM