【问题标题】:Java groupingBy: sum multiple fieldsJava groupingBy:对多个字段求和
【发布时间】:2018-07-21 04:12:16
【问题描述】:

这个问题是帖子的扩展:Java 8 groupingby with returning multiple field

对于同样的问题,如何返回Customer的列表?例如,它应该返回:

Customer("A",4500,6500)
Customer("B",3000,3500)
Customer("C",4000,4500)

【问题讨论】:

  • 您想返回 ListCustomer 对象吗?
  • 好吧,如果它与链接帖子的问题相同,但它应该返回Map<String, List<Double>>而不是List<Customer>,那么结果应该是[Customer{name='A', total=3500.0, balance=5000.0}, Customer{name='B', total=3000.0, balance=3500.0}, Customer{name='C', total=4000.0, balance=4500.0}]。您对客户“A”的计算似乎不正确?
  • A 的计算是正确的。原问题中 A 有 3 个对象

标签: java java-8 java-stream collectors


【解决方案1】:

使用以下代码:

Map<String, Customer> retObj =
    listCust.stream()
        .collect(Collectors.groupingBy(Customer::getName,
                    Collector.of(
                        Customer::new,
                        (c1, c2) -> {
                            c1.setName(c2.getName());
                            c1.setTotal(c1.getTotal() + c2.getTotal());
                            c1.setBalance(c1.getBalance() + c2.getBalance());
                        },
                        (c3, c4) -> {
                            c3.setTotal(c3.getTotal() + c4.getTotal());
                            c3.setBalance(c3.getBalance() + c4.getBalance());
                            return c3;
                        })));

System.out.println(retObj);
System.out.println(retObj.values());       //If you want only the list of all Customers

输出:

{
 A=Customer [name=A, total=4500.0, balance=6500.0], 
 B=Customer [name=B, total=3000.0, balance=3500.0], 
 C=Customer [name=C, total=4000.0, balance=4500.0]
}

【讨论】:

  • 请注意,这会改变 Stream 中的对象 - 您可能不想要。您最好使用自定义 Collector 来创建新的 Customer
  • 我真的不喜欢这里使用toMap,而不是groupingBy
【解决方案2】:

如果您想要Map&lt;String, Customer&gt; 作为结果集 +1,@Pankaj Singhal 的帖子是正确的想法。但是,我会将合并逻辑提取到它自己的函数中,例如在Customer 类中,您将拥有这样的功能:

public static Customer merge(Customer first, Customer second) {
        first.setTotal(first.getTotal() + second.getTotal());
        first.setBalance(first.getBalance() + second.getBalance());
        return first;
}

那么流查询会变成:

Map<String, Customer> retObj = 
           listCust.stream()
                   .collect(Collectors.toMap(Customer::getName, Function.identity(), Customer::merge)); 
  • listCust.stream() 创建一个 stream 对象,即 Stream&lt;Customer&gt;
  • collect 对元素执行可变归约操作 此流使用提供的Collector
  • toMap 的结果是提供的收集器,toMap 方法提取键 Customer::getName 和值 Function.identity(),如果映射的键包含重复项,则使用合并函数 Customer::merge 解决冲突。

我看到将合并逻辑提取到它自己的函数中有三个好处:

  • 代码更紧凑。
  • 代码更具可读性。
  • 合并的复杂性与流隔离。

但是,如果您的意图是检索Collection&lt;Customer&gt;

Collection<Customer> result = listCust.stream()
                .collect(Collectors.toMap(Customer::getName,
                        Function.identity(),
                        Customer::merge))
                .values();

List&lt;Customer&gt; 作为结果集,那么您所要做的就是调用values() 并将其结果传递给ArrayList 构造函数:

List<Customer> result = new ArrayList<>(listCust.stream()
                .collect(Collectors.toMap(Customer::getName,
                        Function.identity(),
                        Customer::merge))
                .values());

更新:

如果您不想改变源中的对象,则只需修改merge 函数,如下所示:

public static Customer merge(Customer first, Customer second) {
        Customer customer = new Customer(first.getName(), first.getTotal(), first.getBalance());
        customer.setTotal(customer.getTotal() + second.getTotal());
        customer.setBalance(customer.getBalance() + second.getBalance());
        return customer;
}

其他一切都保持原样。

【讨论】:

  • 与其他答案的评论相同,这会改变 Stream 中的对象 - 您可能不想要。
  • 现在你为每个合并操作创建一个新的Customer - 相当hacky。
  • @BoristheSpider 嗯,我不会说“hacky”,而是说“次优”。 +1 因为您的回答似乎避免了这种情况,即使我们不知道 OP 是否真的关心改变源中的对象:( .
  • 一般,次优。我认为我们已经涵盖了所有基础 - 我们将看到 OP 正在尝试做什么。不过我认为,这个问题只是出于好奇而不是实际问题——从它的措辞来看。
【解决方案3】:

这里的other answers 很棒,但是它们会改变输入中的Customer 实例,这可能是出乎意料的。

为避免这种情况,请使用自定义 Collector

首先,创建一个返回 Collector 的方法,该方法接受 Stream&lt;Customer&gt; 并将它们合并为单个 Customer

public static Collector<Customer, Customer, Customer> customerCollector() {
    return Collector.of(Customer::new, TestBench::merge,
            (l, r) -> {
                merge(l, r);
                return l;
            });
}

public static void merge(final Customer first, final Customer second) {
    first.setName(second.getName());
    first.setTotal(first.getTotal() + second.getTotal());
    first.setBalance(first.getBalance() + second.getBalance());
}

这假设 Customer 有一个 noargs 构造函数。

那么你可以这样做:

Collection<Customer> result = listCust.stream()
        .collect(groupingBy(Customer::getName, customerCollector()))
        .values();

请注意,我使用 groupingBy 而不是 toMap - groupingBy 收集器专门设计用于对元素进行分组。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-11-21
    • 2017-09-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多