Java分组依据:对多个字段求和

时间:2018-07-21 04:12:16

标签: java java-8 java-stream collectors

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

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

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

4 个答案:

答案 0 :(得分:3)

使用以下代码:

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]
}

答案 1 :(得分:3)

如果您想要Map<String, Customer>作为结果集+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<Customer>
  • collect对以下元素执行可变归约运算 该流使用提供的Collector
  • toMap的结果是提供的收集器,toMap方法提取键Customer::getName和值Function.identity(),如果映射的键包含重复项,则合并功能{ {1}}用于解决冲突。

将合并逻辑提取到其自身的功能中,我看到了三个好处:

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

但是,如果您打算检索Customer::merge

Collection<Customer>

Collection<Customer> result = listCust.stream() .collect(Collectors.toMap(Customer::getName, Function.identity(), Customer::merge)) .values(); 作为结果集,那么您所要做的就是调用List<Customer>并将其结果传递给values()构造函数:

ArrayList

更新

如果您不想更改源中的对象,则只需修改List<Customer> result = new ArrayList<>(listCust.stream() .collect(Collectors.toMap(Customer::getName, Function.identity(), Customer::merge)) .values()); 函数,如下所示:

merge

一切都保持不变。

答案 2 :(得分:2)

这里的other answers很不错,但是它们会使输入中的Customer实例发生突变,这可能是意外的。

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

首先,创建一个返回一个Collector的{​​{1}}并将其合并为单个Stream<Customer>的方法:

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()); } 具有noargs构造函数。

那么您可以做:

Customer

请注意,我使用Collection<Customer> result = listCust.stream() .collect(groupingBy(Customer::getName, customerCollector())) .values(); 而不是groupingBy-toMap收集器专门用于对元素进行分组。

答案 3 :(得分:0)

这可以做到,

List<Customer> result = customers.stream().collect(Collectors.collectingAndThen(
        Collectors.toMap(Customer::getName, c -> Arrays.asList(c.getTotal(), c.getBalance()),
                (l1, l2) -> IntStream.range(0, l1.size()).mapToDouble(i -> l1.get(i) + l2.get(i)).boxed()
                        .collect(Collectors.toList())),
        m -> m.entrySet().stream().map(e -> new Customer(e.getKey(), e.getValue().get(0), e.getValue().get(1)))
                .collect(Collectors.toList())));

首先创建Map,然后使用Finisher函数通过List计算Customer个实例中的Map.Entry