4

I am new to Java 8 and did find some ways to do addition, multiply and subtraction. I will be posting question for add only.

I have written below code and gathering output in Sum1 and Sum2. Both the methods reduce(0, Integer::sum) and .reduce(0, (a, b) -> a+b); gives the same result. What would be the best method to use performance wise and if using large integer values and why ?

List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);

Integer sum1 = numbers.stream().reduce(0, (a, b) -> a+b);
System.out.println("SUM ="+sum1);

Integer product = numbers.stream().reduce(0, (a, b) -> a*b);
System.out.println("PRODUCT = "+product);

int sum2 = numbers.stream().reduce(0, Integer::sum);
System.out.println("SUM 2= "+sum2);

Optional<Integer> sum3 = numbers.stream().reduce((a, b) -> (a + b));
System.out.println("SUM3="+sum3);

// Updated as per  @Hadi J comment
int sum4 = numbers.stream().mapToInt(Integer::intValue).sum();
System.out.println("Sum ="+sum4);
ETO
  • 6,970
  • 1
  • 20
  • 37
Jeff Cook
  • 7,956
  • 36
  • 115
  • 186
  • 7
    I think the best method is use `numbers.stream().mapToInt(Integer::intValue).sum();` because `reduce` has multiple autoboxing. – Hadi J Oct 09 '18 at 08:12
  • Oh Ok. I missed to figure out this way :) Any strong reason to use this ? If you can guide further ? UPDATED my statement – Jeff Cook Oct 09 '18 at 08:13
  • see [this](https://stackoverflow.com/questions/30125296/how-to-sum-a-list-of-integers-with-java-streams) – Hadi J Oct 09 '18 at 08:16
  • Cool, Very nice – Jeff Cook Oct 09 '18 at 08:22
  • 2
    the fastest way would be a plain loop, not streams – Eugene Oct 09 '18 at 08:40
  • 1
    There is no difference between `reduce(0, Integer::sum)` and `reduce((a, b) -> a + b)`. For the first, the method `Integer.sum(…)` will be invoked on every evaluation, for the second, a method created within your class will be invoked, which does exactly the same, `return a+b;`. The considerations are similar to [Function.identity() or t->t](https://stackoverflow.com/a/28041480/2711488). While we have no sharing of the generated instance here (yet), calling an existing method reduces code and raises the likelihood of ending up at optimized shared code, but may hinder debugging. – Holger Oct 09 '18 at 09:13
  • Worth adding that every line which has `(a, b) -> a + b` is a different lambda making the call to this quickly metamorphic when using `Integer::sum` repeatedly doesn't. – Peter Lawrey Oct 18 '18 at 18:25
  • Another difference is that for `Integer::sum`, the two arguments are `int` type, but for `(a, b) -> a + b` the `a` and `b` are Integer type. This could result in one being more optimised than the other for some odd reason. – Peter Lawrey Oct 18 '18 at 18:28

2 Answers2

5

What would be the best method to use performance wise and if using large integer values and why ?

The main idea is to ignore auto-boxing/unboxing when juggling with primitives and their object representations.

Adding two Integer objects is much more sophisticated than adding two int primitives. You can find more details in this post.

So the best case is when you have a primitive array int[] and sum its elements into an accumulator of type int. Thus no boxing involved here.

A worse (but not the worst) case is when you sum an object Integer with a primitive int. It will cause unboxing of the first argument. This is how it works when your initial array or collection contains objects (e.g. Integer[], List, etc...) and the accumulator is primitive. This will cause unboxing of each element of your collection exactly once, while the accumulator will remain the same.

The worst case is summing up a collection of objects into an Integer accumulator.

  1. Plain old Java:

    • Summing Integer[] into Integer

      Integer[] array = {0,1,2,3,4,5,6,7,8,9};
      Integer sum = 0;
      // Each element of the array will be unboxed exactly once
      // sum will be unboxed 10 times
      // result of the sum will be boxed 10 times
      for(int i : array) sum += i;
      
    • Summing Integer[] into int

      Integer[] array = {0,1,2,3,4,5,6,7,8,9};
      int sum = 0;
      // Each element of the array will be unboxed exactly once
      // result of the sum will be boxed 10 times
      for(int i : array) sum += i;
      
    • Summing int[] into int

      int[] array = {0,1,2,3,4,5,6,7,8,9};
      int sum = 0;
      // No boxing/unboxing at all
      for(int i : array) sum += i;
      
  2. Stream API

    • Reducing sum of Stream<Integer>

      Stream<Integer> stream = Stream.of(0,1,2,3,4,5,6,7,8,9);
      // Each element will be unboxed exactly once
      // The accumulator will be unboxed 10 times
      // The result of the sum operation will be boxed 10 times
      Integer sum = stream.reduce(0, Integer::sum);
      
    • Reducing sum of Stream<Integer> mapping to primitive int

      Stream<Integer> stream = Stream.of(0,1,2,3,4,5,6,7,8,9);
      // Each element will be unboxed exactly once
      // Neither accumulator not result will be unboxed/boxed
      int sum = stream.mapToInt(Integer::intValue).reduce(0, Integer::sum);
      
    • Reducing sum of IntStream

      IntStream stream = IntStream.of(0,1,2,3,4,5,6,7,8,9);
      // No boxing/unboxing at all
      int sum = stream.reduce(0, Integer::sum);
      // same as
      int sum2 = stream.sum();
      
ETO
  • 6,970
  • 1
  • 20
  • 37
0

Something interesting: In Intellij IDEA, when you pass the mouse over the sum() function, there is a ToolTip like this one:

enter image description here

Returns the sum of the elements in this stream. This is a special case of a reduction and is equivalent to:

Public abstract int sum() 
//equal function to
return reduce(0, Integer::sum);
Shoniisra
  • 621
  • 7
  • 6