MapToDouble()对于用 Java8流求和 List < Double > 真的有必要吗?

据我所知,使用 Java8流对 List<Double>求和的方法是这样的:

List<Double> vals = . . . ;
double sum = vals.stream().mapToDouble(Double::doubleValue).sum();

在我看来,mapToDouble(Double::doubleValue)似乎有点难以驾驭——只是那种 Lambdas 和 stream 应该摒弃的样板“仪式”。

最佳实践告诉我们,与数组相比,我们更喜欢 List实例,然而对于这种求和,数组似乎更清晰:

double[] vals = . . . ;
double sum = Arrays.stream(vals).sum();

诚然,人们可以这样做:

List<Double> vals = . . . ;
double sum = vals.stream().reduce(0.0, (i,j) -> i+j);

但是 reduce(....)sum()长得多。

我知道这与需要围绕 Java 的非对象原语对流进行改造的方式有关,但我还是漏掉了什么吗?有没有什么办法可以缩短这段时间?或者这只是目前的最新技术?


最新消息-答案摘要

下面是答案摘要。虽然我在这里有一个总结,我敦促读者仔细阅读的答案本身充分。

@ dasblinkenlight 解释说,由于在 Java 历史上做出的决定,特别是泛型的实现方式以及它们与非对象原语的关系,总是有必要进行某种类型的拆箱。他指出,从理论上讲,编译器可以凭直觉拆箱,并允许代码更简短,但这还没有实现。

@ Holger 展示了一个非常接近我所问的表达能力的解决方案:

double sum = vals.stream().reduce(0.0, Double::sum);

我不知道新的静态 Double.sum()方法。加上1.8,似乎正是为了达到我所描述的目的。我还找到了 Double.min()Double.max()。展望未来,我一定会在 List<Double>等类似的操作中使用这个习惯用法。

51168 次浏览

在我看来,mapToDouble(Double::doubleValue)似乎是兰姆达和数据流应该摒弃的东西。

需要使用 mapToDouble是决定通过类型擦除实现泛型的结果,实质上关闭了在泛型内部使用原语的任何可能性。正是这个决定使得有必要创建 DoubleStreamIntStreamLongStream类家族——以提供基于流的解压缩。

有没有什么办法可以缩短这段时间?或者这只是目前的最新技术?

不幸的是,现在还不行: 虽然理论上编译器可以指出 Stream<Double>可以隐式地转换为 DoubleStream,就像原语可以取消装箱一样,但是还没有做到这一点。

就基于数组的解决方案而言,它是三者中效率最高的。但是,它不像其他两个方法那样灵活: 使用 mapToDouble的方法可以对定制类的任何属性求和,而最后一个方法可以执行其他类型的聚合。

reduce(....)sum()长得多

我同意,这种方法在可读性方面比 mapToDouble差。

有没有什么办法可以缩短这段时间?

有的,你可以简单地写:

double sum = vals.stream().mapToDouble(d->d).sum();

这使得拆箱变得隐含,但当然不会提高效率。

由于 List 已装箱,拆箱是不可避免的。另一种方法是:

double sum = vals.stream().reduce(0.0, Double::sum);

它不执行 mapToDouble,但仍然允许将代码读取为“ ... sum”。

还有另一种方法。如果你只需要在 DoubleIntegerLong的列表上有一个和、平均值、最小值、最大值等,你可以使用其中一个可用的 Collectors,例如:

List<Double> doubles = Arrays.asList(3.14, 5.15, 4.12, 6.);
System.out.println(
doubles.stream()
.collect(Collectors.summingDouble(d -> d))
);

会打印 18.41

注意,方法名称是 翻倍,还有一个方法叫做 总结双倍,返回 DoubleSummaryStatistics,包含所有基本的数学运算结果。