有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

lambda Java stream groupingBy和sum多个字段

这是我的愚人名单

class Foo {
    private String name;
    private int code;
    private int account;
    private int time;
    private String others;

    ... constructor, getters & setters
}

例如(账户的所有值都已设置为1)

new Foo(First, 200, 1, 400, other1), 
new Foo(First, 200, 1, 300, other1),
new Foo(First, 201, 1, 10, other1),
new Foo(Second, 400, 1, 20, other2),
new Foo(Second, 400, 1, 40, other2),
new Foo(Third, 100, 1, 200, other3),
new Foo(Third, 101, 1, 900, other3)

我想通过对“名称”和“代码”进行分组、计算数字以及对“时间”求和来转换这些值,例如

new Foo(First, 200, 2, 700, other1), 
new Foo(First, 201, 1, 10, other1),
new Foo(Second, 400, 2, 60, other2),
new Foo(Third, 100, 1, 200, other3),
new Foo(Third, 101, 1, 900, other3)

我知道我应该使用这样的流:

Map<String, List<Foo>> map = fooList.stream().collect(groupingBy(Foo::getName()));

但是,我怎样才能按代码对它们进行分组,然后进行计算和汇总呢


还有,如果我想计算平均时间呢?e、 g

new Foo(First, 200, 2, 350, other1), 
new Foo(First, 201, 1, 10, other1),
new Foo(Second, 400, 2, 30, other2),
new Foo(Third, 100, 1, 200, other3),
new Foo(Third, 101, 1, 900, other3)

我可以同时使用summingInt(Foo::getAccount)averagingInt(Foo::getTime)


共 (2) 个答案

  1. # 1 楼答案

    一种解决方法是在映射回对象类型时处理grouping键为Listgrouping和强制转换

    List<Foo> result = fooList.stream()
            .collect(Collectors.groupingBy(foo ->
                            Arrays.asList(foo.getName(), foo.getCode(), foo.getAccount()),
                    Collectors.summingInt(Foo::getTime)))
            .entrySet().stream()
            .map(entry -> new Foo((String) entry.getKey().get(0),
                    (Integer) entry.getKey().get(1),
                    entry.getValue(),
                    (Integer) entry.getKey().get(2)))
            .collect(Collectors.toList());
    

    更干净的方法是为merge函数公开api并执行toMap


    Edit:使用toMap进行简化如下

    List<Foo> result = new ArrayList<>(fooList.stream()
            .collect(Collectors.toMap(foo -> Arrays.asList(foo.getName(), foo.getCode()),
                    Function.identity(), Foo::aggregateTime))
            .values());
    

    其中aggregateTimeFoo中的静态方法,如下所示:

    static Foo aggregateTime(Foo initial, Foo incoming) {
        return new Foo(incoming.getName(), incoming.getCode(),
                incoming.getAccount(), initial.getTime() + incoming.getTime());
    }
    
  2. # 2 楼答案

    您可以实现自己的collect机制,如下所示

            var collect = Stream.of(
                    new Foo(1, 200, 1, 400),
                    new Foo(1, 200, 1, 300),
                    new Foo(1, 201, 1, 10),
                    new Foo(2, 400, 1, 20),
                    new Foo(2, 400, 1, 40),
                    new Foo(3, 100, 1, 200),
                    new Foo(3, 101, 1, 900)
            )
                    .collect(
                            ArrayList::new,
                            (BiConsumer<ArrayList<Foo>, Foo>) (foos, foo) -> {
                                var newFoo = foos
                                        .stream()
                                        .filter(f -> f.name == foo.name && f.account == foo.account)
                                        .findFirst()
                                        .map(f -> new Foo(f.name, f.code, f.account + foo.account, f.time + foo.time))
                                        .orElse(foo);
                                foos.removeIf(f -> f.name == foo.name && f.account == foo.account);
                                foos.add(newFoo);
                            },
                            (foos, foos2) -> foos.addAll(foos2)
                    );