为什么原始Stream没有收集(collections家)?

我正在为新手程序员编写一个库,所以我试图保持API尽可能干净。

我的库需要做的一件事就是对大量的int或long进行一些复杂的计算。 我的用户需要从中计算这些值所需的大量场景和业务对象,因此我认为最好的方法是使用流来允许用户将业务对象映射到IntStreamLongStream ,然后计算收集器内的计算。

但是IntStream和LongStream只有3参数collect方法:

 collect(Supplier supplier, ObjIntConsumer accumulator, BiConsumer combiner) 

并且没有Stream具有的简化collect(Collector)方法。

所以不是能够做到的

 Collection businessObjs = ... MyResult result = businessObjs.stream() .mapToInt( ... ) .collect( new MyComplexComputation(...)); 

我必须提供这样的供应商,累加器和合并器:

 MyResult result = businessObjs.stream() .mapToInt( ... ) .collect( ()-> new MyComplexComputationBuilder(...), (builder, v)-> builder.add(v), (a,b)-> a.merge(b)) .build(); //prev collect returns Builder object 

对于我的新手用户来说这太复杂了,而且非常容易出错。

我的工作是创建以IntStreamLongStream作为输入的静态方法,并为您隐藏收集器的创建和执行

 public static MyResult compute(IntStream stream, ...){ return .collect( ()-> new MyComplexComputationBuilder(...), (builder, v)-> builder.add(v), (a,b)-> a.merge(b)) .build(); } 

但这并不符合使用Streams的常规惯例:

 IntStream tmpStream = businessObjs.stream() .mapToInt( ... ); MyResult result = MyUtil.compute(tmpStream, ...); 

因为你必须保存一个临时变量并将其传递给静态方法,或者在静态调用中创建Stream,当它与我的计算中的其他参数混合时可能会令人困惑。

在使用IntStreamLongStreamIntStream LongStream吗?

事实上我们做了一些Collector.OfXxx专业化的原型。 我们发现 – 除了明显的更专业类型的烦恼之外 – 如果没有完整的原始专用集合(如Trove,或者GS-Collections,但是JDK所做的那样),这实际上并不是非常有用。没有)。 例如,没有IntArrayList,Collector.OfInt只是将拳击推送到其他地方 – 从收集器到容器 – 没有大的胜利,以及更多的API表面。

也许如果使用方法引用而不是lambdas,原始流收集所需的代码似乎并不复杂。

 MyResult result = businessObjs.stream() .mapToInt( ... ) .collect( MyComplexComputationBuilder::new, MyComplexComputationBuilder::add, MyComplexComputationBuilder::merge) .build(); //prev collect returns Builder object 

在Brian 对这个问题的明确答案中 ,他提到了另外两个Java集合框架,这些框架确实有原始集合,实际上可以与原始流上的collect方法一起使用。 我认为说明如何在这些框架中使用原始流的原始容器的一些示例可能是有用的。 下面的代码也适用于并行流。

 // Eclipse Collections List integers = Interval.oneTo(5).toList(); Assert.assertEquals( IntInterval.oneTo(5), integers.stream() .mapToInt(Integer::intValue) .collect(IntArrayList::new, IntArrayList::add, IntArrayList::addAll)); // Trove Collections Assert.assertEquals( new TIntArrayList(IntStream.range(1, 6).toArray()), integers.stream() .mapToInt(Integer::intValue) .collect(TIntArrayList::new, TIntArrayList::add, TIntArrayList::addAll)); 

注意:我是Eclipse Collections的提交者。

如果存在缺少的方法,则将原始流转换为盒装对象流。

 MyResult result = businessObjs.stream() .mapToInt( ... ) .boxed() .collect( new MyComplexComputation(...)); 

或者不要首先使用原始流,而是一直使用Integer

 MyResult result = businessObjs.stream() .map( ... ) // map to Integer not int .collect( new MyComplexComputation(...)); 

我在我的库StreamEx中实现了原始收集器(从版本0.3.0开始)。 有接口IntCollectorLongCollectorDoubleCollector扩展了Collector接口,专门用于处理原语。 组合过程还有一个额外的细微差别,如IntStream.collect方法接受BiConsumer而不是BinaryOperator

有一堆预定义的集合方法可以将数字连接到字符串,存储到原始数组, BitSet ,查找min,max,sum,计算汇总统计信息,执行group-by和partition-by操作。 当然,您可以定义自己的collections家。 这里有几个用法示例(假设您有输入数据的int[] input数组)。

使用分隔符将数字连接为字符串

 String nums = IntStreamEx.of(input).collect(IntCollector.joining(",")); 

按最后一位分组:

 Map groups = IntStreamEx.of(input) .collect(IntCollector.groupingBy(i -> i % 10)); 

分别对正数和负数求和:

 Map sums = IntStreamEx.of(input) .collect(IntCollector.partitioningBy(i -> i > 0, IntCollector.summing())); 

这是一个简单的基准测试 ,可以比较这些收集器和通常的对象收集器。

请注意,我的库不提供(并且将来不会提供)任何用户可见的数据结构,例如基元上的映射,因此将分组执行到通常的HashMap 。 但是,如果您使用Trove / GS / HFTC /无论如何,为这些库中定义的数据结构编写其他原始收集器以获得更高性能并不困难。

Geotz先生提供了明确的答案,说明为什么决定不包括专门的收集者 ,但是,我想进一步调查这个决定对这一决定的影响程度。

我以为我会把结果作为答案发布。

我使用jmh microbenchmark框架来计算使用两种收集器计算大小为1,100,1000,10000和100万的集合的计算所需的时间:

 @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @State(Scope.Thread) public class MyBenchmark { @Param({"1", "100", "1000", "100000", "1000000"}) public int size; List seqs; @Setup public void setup(){ seqs = new ArrayList(size); Random rand = new Random(); for(int i=0; i< size; i++){ //these lengths are random but over 128 so no caching of Longs seqs.add(BusinessObjFactory.createOfRandomLength()); } } @Benchmark public double objectCollector() { return seqs.stream() .map(BusinessObj::getLength) .collect(MyUtil.myCalcLongCollector()) .getAsDouble(); } @Benchmark public double primitiveCollector() { LongStream stream= seqs.stream() .mapToLong(BusinessObj::getLength); return MyUtil.myCalc(stream) .getAsDouble(); } public static void main(String[] args) throws RunnerException{ Options opt = new OptionsBuilder() .include(MyBenchmark.class.getSimpleName()) .build(); new Runner(opt).run(); } } 

结果如下:

 # JMH 1.9.3 (released 4 days ago) # VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0_31.jdk/Contents/Home/jre/bin/java # VM options:  # Warmup: 20 iterations, 1 s each # Measurement: 20 iterations, 1 s each # Timeout: 10 min per iteration # Threads: 1 thread, will synchronize iterations # Benchmark mode: Average time, time/op # Benchmark: org.sample.MyBenchmark.objectCollector # Run complete. Total time: 01:30:31 Benchmark (size) Mode Cnt Score Error Units MyBenchmark.objectCollector 1 avgt 200 140.803 ± 1.425 ns/op MyBenchmark.objectCollector 100 avgt 200 5775.294 ± 67.871 ns/op MyBenchmark.objectCollector 1000 avgt 200 70440.488 ± 1023.177 ns/op MyBenchmark.objectCollector 100000 avgt 200 10292595.233 ± 101036.563 ns/op MyBenchmark.objectCollector 1000000 avgt 200 100147057.376 ± 979662.707 ns/op MyBenchmark.primitiveCollector 1 avgt 200 140.971 ± 1.382 ns/op MyBenchmark.primitiveCollector 100 avgt 200 4654.527 ± 87.101 ns/op MyBenchmark.primitiveCollector 1000 avgt 200 60929.398 ± 1127.517 ns/op MyBenchmark.primitiveCollector 100000 avgt 200 9784655.013 ± 113339.448 ns/op MyBenchmark.primitiveCollector 1000000 avgt 200 94822089.334 ± 1031475.051 ns/op 

正如您所看到的,原始Stream版本稍微快一点,但即使集合中有100万个元素,它也只有0.05秒(平均)。

对于我的API,我宁愿保持更清晰的对象流约定并使用盒装版本,因为它是如此微小的性能损失。

感谢所有对此问题有所了解的人。