0

我有如下集合元素列表。我想按 productCode 等多个字段分组,然后按 productpurchase 类型分组。

class Product{
    private String productCode;
    // this is ENUM with 2 possible values "ONLINE" or "INSTORE"
    private String productPurchaseType; 
    private String productCost;
    ...
}

可能的输出应该是

ROW1::ProductCode1, Count of ONLINE,Count of INSTORE,Min, Max
ROW2::ProductCode2, Count of ONLINE,Count of INSTORE, Min, Max

我在下面使用了一段代码,但它没有给出 ONLINE 和 INSTORE 的计数

void groupByMerchantMCCCodeZIP(List<Product> productList) {
    Map<String, Map<String, List<Product>>> output = transactionDataList.stream()
        .collect(Collectors.groupingBy(Product::getProductCode,
            Collectors.groupingBy(Product::productPurchaseType)));
    System.out.println(output);
}

从事 java 8 groupby 工作的人可以告诉我最好的方法吗?

谢谢!

4

1 回答 1

1

要聚合多个值,您应该编写自己的Collector,以获得最佳性能。

编写 a 的最简单方法Collector是调用Collector.of()方法,并结合结果收集器类。下面是一个例子。

首先,我重新定义了Product更好的字段类型:

class Product {
    public enum PurchaseType { ONLINE, INSTORE }

    private final String       code;
    private final PurchaseType purchaseType; 
    private final BigDecimal   cost;

    public Product(String code, PurchaseType purchaseType, String cost) {
        this.code = code;
        this.purchaseType = purchaseType;
        this.cost = new BigDecimal(cost);
    }
    public String getCode() {
        return this.code;
    }
    public PurchaseType getPurchaseType() {
        return this.purchaseType;
    }
    public BigDecimal getCost() {
        return this.cost;
    }
}

然后是结果收集器类:

class ProductResult {
    private int        onlineCount;
    private int        instoreCount;
    private BigDecimal minCost;
    private BigDecimal maxCost;

    public void add(Product product) {
        if (product.getPurchaseType() == Product.PurchaseType.ONLINE)
            this.onlineCount++;
        else if (product.getPurchaseType() == Product.PurchaseType.INSTORE)
            this.instoreCount++;
        if (this.minCost == null || product.getCost().compareTo(this.minCost) < 0)
            this.minCost = product.getCost();
        if (this.maxCost == null || product.getCost().compareTo(this.maxCost) > 0)
            this.maxCost = product.getCost();
    }
    public ProductResult merge(ProductResult that) {
        this.onlineCount += that.onlineCount;
        this.instoreCount += that.instoreCount;
        if (this.minCost == null || that.minCost.compareTo(this.minCost) < 0)
            this.minCost = that.minCost;
        if (this.maxCost == null || that.maxCost.compareTo(this.maxCost) > 0)
            this.maxCost = that.maxCost;
        return this;
    }
    @Override
    public String toString() {
        return "[online: " + this.onlineCount +
              ", instore: " + this.instoreCount +
              ", min: " + this.minCost +
              ", max: " + this.maxCost + "]";
    }
    public int getOnlineCount() {
        return this.onlineCount;
    }
    public int getInstoreCount() {
        return this.instoreCount;
    }
    public BigDecimal getMinCost() {
        return this.minCost;
    }
    public BigDecimal getMaxCost() {
        return this.maxCost;
    }
}

演示

List<Product> productList = Arrays.asList(
        new Product("MILK", Product.PurchaseType.ONLINE, "3.99"),
        new Product("MILK", Product.PurchaseType.ONLINE, "3.99"),
        new Product("MILK", Product.PurchaseType.INSTORE, "4.95"),
        new Product("BREAD", Product.PurchaseType.INSTORE, "7.48")
);

Map<String, ProductResult> result = productList.stream()
        .collect(Collectors.groupingBy(Product::getCode,
                    Collector.of(ProductResult::new,
                                 ProductResult::add,
                                 ProductResult::merge,
                                 Characteristics.UNORDERED)));
result.entrySet().forEach(System.out::println);

输出

BREAD=[online: 0, instore: 1, min: 7.48, max: 7.48]
MILK=[online: 2, instore: 1, min: 3.99, max: 4.95]
于 2018-06-28T03:33:11.320 回答