使用收集器执行flatMap操作

The*_*ice 5 java java-8 java-stream

我想了解flatMap使用Collectors 执行一段时间的方法.这是一个例子.

场景:

我有以下接口:

interface Ic {
    //empty
}

interface Ib {
    Stream<Ic> getCs();
}

interface Ia {
    String getName();
    Stream<Ib> getBs();
}
Run Code Online (Sandbox Code Playgroud)

我正在尝试实现以下方法:

Map<String, Long> total_of_C_per_A (Stream<Ia> streamOfA) {
   return streamOfA.collect(groupBy(Ia::getName, ???));
}
Run Code Online (Sandbox Code Playgroud)

分类功能相当straitforward,我的问题是与下游收集器.我需要计算与"A"相关的"C"数.

我试图:

如果我想简单地返回计数而不创建地图,我会这样做:

streamOfA
  .flatMap(Ia::getBs)
  .flatMap(Ib::getCs)
  .count();
Run Code Online (Sandbox Code Playgroud)

但是这个Collectors类只允许我做映射操作.我还能尝试做些什么?

谢谢.

Hol*_*ger 6

这个答案指出你已经进入了正确的方向,但是没有必要嵌套多个mapping收集器,因为你可以将这些函数写入一个lambda表达式.考虑到summingLong收集器需要一个求值的函数long,你可以简单地将该函数传递给收集器而不需要任何mapping收集器:

Map<String, Long> total_of_C_per_A (Stream<Ia> streamOfA) {
    return streamOfA.collect(groupingBy(
            Ia::getName,
            summingLong(ia -> ia.getBs().flatMap(Ib::getCs).count())));
}
Run Code Online (Sandbox Code Playgroud)

这也具有以下优点:long值不会加到Long实例框中.

还有另一种选择flatMap:

Map<String, Long> total_of_C_per_A (Stream<Ia> streamOfA) {
    return streamOfA.collect(groupingBy(
            Ia::getName,
            summingLong(ia -> ia.getBs().mapToLong(ib -> ib.getCs().count()).sum())));
}
Run Code Online (Sandbox Code Playgroud)


hol*_*ava 5

文档将收集器#映射描述为:

通过在累积之前将映射函数应用于每个输入元素,使接受类型元素的收集器适应一个接受类型的U元素T.

mapping()在使用时,收集器是最有用的多级还原,如一个下游groupingBypartitioningBy.

这意味着你可以尽可能地编写任何可能的Collectors.

import static java.util.stream.Collectors.*;

Map<String, Long> total_of_C_per_A(Stream<Ia> streamOfA) {
    return streamOfA.collect(groupingBy(
            Ia::getName,
            mapping(
                    Ia::getBs,
                    mapping(
                            it -> it.flatMap(Ib::getCs),
            //    reduce() does boxing & unboxing ---v
                            mapping(Stream::count, reducing(0L,Long::sum))
                    )
            )
    ));
}
Run Code Online (Sandbox Code Playgroud)

或者使用收藏家#summingLong代替.

Map<String, Long> total_of_C_per_A(Stream<Ia> streamOfA) {
    return streamOfA.collect(groupingBy(
            Ia::getName,
            mapping(
                    Ia::getBs,
                    mapping(
                            it -> it.flatMap(Ib::getCs),
            //    summingLong() does boxing      ---v
                            mapping(Stream::count, summingLong(Long::longValue))
            //      Long::longValue does unboxing operation ---^
                    )
            )
    ));
}
Run Code Online (Sandbox Code Playgroud)

感谢@Holger指出上面代码的潜在问题,你可以简单地使用summingLong(Stream::count)代替.在这种方法是没有必要拳击Stream#count返回long一个Long.并Long::longValue取消装箱Longlong.

Map<String, Long> total_of_C_per_A(Stream<Ia> streamOfA) {
    return streamOfA.collect(groupingBy(
        Ia::getName,
        mapping(
            Ia::getBs,
        //    summingLong() doesn't any boxing ---v
            mapping(it -> it.flatMap(Ib::getCs), summingLong(Stream::count))
        )
    ));
}
Run Code Online (Sandbox Code Playgroud)

  • @Eugene:相比什么?`Collectors.counting()`在这里不起作用,`减少(0L,Long :: sum)`仍将在Java 9下装箱.注意,在这个答案中,没有拳击的减少潜力尚未被利用无论如何,因为`mapping(Stream :: count,summingLong(Long :: longValue))`仍然无需任何所有`long`值; 它可以简单地写成`summingLong(Stream :: count)`.但是,如果没有嵌套所有这些"映射"步骤,它就会变得更加简单. (2认同)
  • @Eugene:有一个早期版本.但它并没有做到预期的事情...... (2认同)