如何使用Java Streams对整数列表求和?

480

我想对整数列表进行求和。以下是代码示例,但语法不太对。这段代码能否进行优化?

Map<String, Integer> integers;
integers.values().stream().mapToInt(i -> i).sum();

7
您认为语法不正确的原因是什么?这是通常的习语。也许根据您的映射可能具有的值,您想使用mapToLong来避免溢出。 - Alexis C.
3
个人而言,我认为“i -> i”非常清晰。是的,你需要知道这个值将自动拆箱,但自从Java 5以来就是这样了。 - Alexis C.
4
@AlexisC. 这个代码很好理解,因为它被传递给了mapToInt()函数并且我是一位有经验的开发者。但是,如果没有上下文,i -> i看起来像一个空操作。使用Integer::intValue更长,但是使拆箱操作更加明确。 - JB Nizet
1
@JBNizet 调用方法 foo(int i) 的人不会每次调用它时都写 foo(myInteger.intValue());(或者至少我希望如此!!)。我同意您的看法,即 Integer::intValue 更加明确,但我认为同样适用于这里。人们只需要学习一次,然后就完成了 :-)。这不像是一些神奇的混淆技术。 - Alexis C.
4
@JB Nizet: "i -> i"看起来像是一个no-op(无操作),在概念上,它确实是一个no-op。当然,在底层会调用Integer.intValue() 方法,但更深入底层,该方法会被内联成与源代码中看起来完全一样的no-op。 使用 Integer :: intValue 有额外的好处,即不会在字节码中创建合成方法,但这不应驱动您决定如何组织源代码。 - Holger
显示剩余2条评论
13个回答

640

这样做是可行的,但i -> i执行了一些自动拆箱操作,这就是为什么它“感觉”奇怪的原因。mapToInt将流转换为“基本int值元素”的IntStream。以下任何一个都可以工作,并更好地解释编译器在使用原始语法时正在执行的操作:

integers.values().stream().mapToInt(i -> i.intValue()).sum();
integers.values().stream().mapToInt(Integer::intValue).sum();

4
如果我们有一个 BigInteger,会怎样呢? - GOXR3PLUS
27
一个简单的选项是 BigDecimal sum = numbers.stream().reduce(BigDecimal.ZERO, BigDecimal::add);。该代码使用Java 8中的流式编程,对一个包含BigDecimal数字的列表进行求和操作,并将结果存储在变量sum中。其中,方法reduce()的第一个参数表示初始值为0,第二个参数则是用于累加的函数BigDecimal::add。 - Matt
1
他特别要求“整数”。 - Joaquín L. Robles
2
你如何将数值加总到特定值?例如,你有一个整数列表,但你想加总直到达到值20。 - DaviesTobi alex
1
@JoaquínL.Robles 基本类型在大多数流行的编程语言中很容易发生溢出和下溢,包括Java的Integer类型。 - undefined

181

我建议再增加两个选项:

integers.values().stream().mapToInt(Integer::intValue).sum();
integers.values().stream().collect(Collectors.summingInt(Integer::intValue));
第二种方法使用Collectors.summingInt()收集器,还有一个summingLong()收集器可用于与mapToLong一起使用。
第三种选择:Java 8引入了一个非常有效的LongAdder累加器,旨在加速并行流和多线程环境下的汇总。这里是一个示例用法:
LongAdder a = new LongAdder();
map.values().parallelStream().forEach(a::add);
sum = a.intValue();

97

来自文档

归约操作 归约操作(也叫折叠操作)将一系列输入元素通过重复应用一个组合操作,例如找到一组数字的总和或最大值,或将元素累积到列表中,将它们组合成单个摘要结果。流类具有多种形式的通用归约操作,称为reduce()和collect(),以及多个专门的归约形式,如sum()、max()或count()。

当然,这样的操作可以轻松地实现为简单的顺序循环,就像:

int sum = 0;
for (int x : numbers) {
   sum += x;
}

然而,有很多好的理由选择reduce操作而不是像上面那样进行可变的累加。不仅reduce“更抽象”——它作用于整个流而不是单个元素——而且正确构建的reduce操作本质上是可并行化的,只要用于处理元素的函数是关联和无状态的。例如,给定一个数字流,我们想要找到总和,我们可以编写:

int sum = numbers.stream().reduce(0, (x,y) -> x+y);

或者:

int sum = numbers.stream().reduce(0, Integer::sum);

这些缩减操作几乎不需要修改就可以安全地并行运行:

int sum = numbers.parallelStream().reduce(0, Integer::sum);
所以,对于一张地图,您会使用:

integers.values().stream().mapToInt(i -> i).reduce(0, (x,y) -> x+y);

或者:

integers.values().stream().reduce(0, Integer::sum);

2
OP所拥有的代码更好,也更清晰。这段代码将涉及大量的拆箱和装箱操作。 - JB Nizet
1
@JBNizet 除非逃逸分析消除了装箱,否则你必须尝试一下才能确定它是否可行。 - Peter Lawrey
6
(x,y) -> x+y 需要把 x 和 y 拆箱计算后再装箱得出结果。然后继续逐个将流中的下一个元素与结果相加,一遍又一遍。 - JB Nizet
3
Integer::sum 也存在相同的问题。如果你使用 mapToInt() 方法获得一个 IntStream,那么在它上面调用 sum() 方法比调用 reduce() 方法更加简单明了。 - JB Nizet
3
请参见 http://docs.oracle.com/javase/8/docs/api/java/lang/Integer.html#sum-int-int-。 Integer.sum() 的两个参数都是 int 类型。因此,必须对流中的两个 Integers 进行取消装箱以将它们作为参数传递给该方法。该方法返回一个 int,但 reduce() 方法需要一个 BinaryOperator<Integer> 作为参数,因此返回 Integer。因此,求和结果必须转换为 Integer。 - JB Nizet
显示剩余4条评论

35

你可以使用reduce方法:

long sum = result.stream().map(e -> e.getCreditAmount()).reduce(0L, (x, y) -> x + y);
long sum = result.stream().map(e -> e.getCreditAmount()).reduce(0L, Integer::sum);

9
已经有了针对 int 的这样的累加器,它是 Integer::sum - Alex Salauyou
1
你正在返回一个长整型,因此最好使用 Long::sum 而不是 Integer::sum - Andrei Damian-Fekete

20

你可以使用reduce()函数来对一个整数列表求和。

int sum = integers.values().stream().reduce(0, Integer::sum);

15
你可以使用collect方法来添加整数列表。
List<Integer> list = Arrays.asList(2, 4, 5, 6);
int sum = list.stream().collect(Collectors.summingInt(Integer::intValue));

10
我已经声明了一个整数列表。
ArrayList<Integer> numberList = new ArrayList<Integer>(Arrays.asList(1, 2, 3, 4, 5));

你可以尝试使用以下不同的方法。
使用mapToInt
int sum = numberList.stream().mapToInt(Integer::intValue).sum();

使用summarizingInt
int sum = numberList.stream().collect(Collectors.summarizingInt(Integer::intValue)).getSum();

使用 reduce

int sum = numberList.stream().reduce(Integer::sum).get().intValue();

7

希望这篇文章能够帮助那些需要对列表中的对象进行操作的人。

如果你有一个对象列表,并且想要对其中特定字段进行求和,请使用以下方法。

List<ResultSom> somList = MyUtil.getResultSom();
BigDecimal result= somList.stream().map(ResultSom::getNetto).reduce(
                                             BigDecimal.ZERO, BigDecimal::add);

6
这是简洁地总结int类型数组的最短方式(对于long数组,使用LongStream,对于double数组,使用DoubleStream等)。然而,并非所有原始整数或浮点类型都有Stream实现。
IntStream.of(integers).sum();

很遗憾,我们没有任何int数组。因此,对于这个问题,“IntStream.of()”不起作用,除非我们像这样制作一些令人毛骨悚然的东西: IntStream.of(integers.values().stream().mapToInt(Integer::intValue).toArray())。sum(); - Kaplan
1
integers.values().stream().mapToInt( Integer::intValue ).sum() - Sachith Dickwella

2

这里还有一个选项,与多核环境的使用有关。如果您想利用它的优势,那么应该使用下面的代码而不是其他提到的解决方案:

int sum = integers.values().parallelStream().mapToInt(Integer::intValue)
        .reduce(0, Integer::sum, Integer::sum);

这个解决方案与其他方案类似,但请注意 reduce 中的第三个参数。它告诉编译器如何处理由不同线程在流的不同块中计算出来的部分汇总。此外,使用的是 parallelStream() 而不是 stream()。在这种情况下,它只会对其进行汇总。作为第三个参数放置的另一个选项是 (i, j) -> i + j,这意味着它将把流块的值(j)添加到当前值(i)并将其用作下一个流块的当前值,直到处理完所有部分结果。
即使使用普通的 stream(),也有必要告诉 reduce 如何处理流块的汇总,以防以后有人或您想并行化它。最初的开发是最好的时机,因为以后您需要记住这是什么,并需要花费一些时间来理解该代码的目的。
当然,您可以使用不同的 lambda 方言代替方法引用运算符。我更喜欢这种方式,因为它更紧凑但仍然易于阅读。
还要记住,这也可以用于更复杂的计算,但始终要注意没有关于流元素序列和部署的保证。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接