10

Why is the second code (the one with the stream) a better solution than the first?

First :

public static void main(String [] args) {
   List<Integer> values = Arrays.asList(1,2,3,4,5,6);
   int total = 0;
   for(int e : values) {
       total += e * 2;

   }

Second :

   System.out.println(total);
   System.out.println(
           values.stream()
           .map(e-> e*2)
           .reduce(0, (c, e)-> c + e)); 
Nic Szerman
  • 1,614
  • 15
  • 22
codeme
  • 591
  • 1
  • 9
  • 26
  • 2
    For me the first one is more understandable. – Zelldon Jun 05 '15 at 11:51
  • 1
    *"Why the second part of the code (with the stream) a better solution then the first?"* According to ***whom***? – T.J. Crowder Jun 05 '15 at 11:54
  • seems like lambda expression start confusing people – Muhammad Suleman Jun 05 '15 at 11:56
  • @T.J Crowder according to https://youtu.be/j9nj5dTo54Q?t=28m42s . I could not understand the tutors explanation. – codeme Jun 05 '15 at 12:06
  • A simpler version of #2 would be `values.stream().mapToInt(e -> e * 2).sum()`, which might be easier to follow. – Louis Wasserman Jun 05 '15 at 17:11
  • 4
    For a seasoned C++ programmer lambda is new and thus unfamiliar. Of course the first one is easier if you've been programming C++ for a while. You're used to the first gibberish and not the second. If you have been programming some functional languages though the second would be just as easy to read since `map` and `reduce` (`fold`) is home ground. – Sylwester Jun 05 '15 at 18:29
  • 4
    The "functional" version isn't comparable because you've split the multiplication and addition into separate steps, whereas the "imperative" does both in one step. Also, you use the unclear variable `c` instead of `total`. The functional version should've been: `values.stream().reduce(0, (total, e)-> total + e*2))` – Kelvin Feb 24 '17 at 19:24
  • In this simple isolated example, both are about the same. But when the code is making 10 different changes to `total`, each with their own specialized complex logic, and see if it's still easy to understand. Then `total` is no longer isolated, and you need run through all the changes in your mind to understand what the current state is/should be. – Kelvin Feb 24 '17 at 19:35

3 Answers3

17

Mutation is changing an object and is one common side effect in programming languages.

A method that has a functional contract will always return the same value to the same arguments and have no other side effects (like storing file, printing, reading). Thus even if you mutate temporary values inside your function it's still pure from the outside. By putting your first example in a function demonstrates it:

public static int squareSum(const List<Integer> values)
{
    int total = 0;
    for(int e : values) {
        total += e * 2;  // mutates a local variable
    }
    return total;
}

A purely functional method doesn't even update local variables. If you put the second version in a function it would be pure:

public static int squareSum(const List<Integer> values)
{
    return values.stream()
           .map(e-> e*2)
           .reduce(0, (c, e)-> c + e);
}

For a person that knows other languages that has long been preferring a functional style map and reduce with lambda is very natural. Both versions are easy to read and easy to test, which is the most important part.

Java has functional classes. java.lang.String is one of them.

uzay95
  • 14,378
  • 28
  • 105
  • 167
Sylwester
  • 44,544
  • 4
  • 42
  • 70
5

Mutation is changing the state of an object, either the list or some custom object.

Your particular code does not cause a mutation of the list either way, so there's no practical benefit here of using lambdas instead of plain old iteration. And, blame me, but I would use the iteration approach in this case.

Some approaches say that whenever you need to modify an object/collection, you need to return a new object/collection with the modified data instead of changing the original one. This is good for collection for example when you concurrently access a collection and it's being changed from another thread.

Of course this could lead to memory leaks, so there are some algorithms for managing memory and mutability for collection i.e. only the changed nodes are stored in another place in memory.

Royal Bg
  • 6,854
  • 1
  • 16
  • 24
5

While Royal Bg is right you're not mutating your data in either case, it's not true that there's no advantage to the second version. The second version can be heavily multithreaded without ambiguity.

Since we're not expecting to iterate the list we can put the operations into a heavily multi-threaded context and solve it on a gpu. In the latter one each data point in the collection is multiplied by 2. Then reduced (which means every element is added together), which can be done by a reduction.

There are a number of potential advantages to the latter code not seen in the former. And while neither code element actually mutates, in the second one we are given the very clear contract that the items cannot mutate while that is happening. So we know that it doesn't matter if we iterate the list forwards, backwards, or apply it multithreaded etc. The implementation details can be filled in later. But, only if we know mutation can't happen and streams simply don't allow them.

Tatarize
  • 8,825
  • 4
  • 53
  • 56