3

Unable to understand how "Operations on a stream produce a result, but do not modify its underlying data source" with reference to java 8 streams.

shapes.stream() 
      .filter(s -> s.getColor() == BLUE)
      .forEach(s -> s.setColor(RED));

As per my understanding, forEach is setting the color of object from shapes then how does the top statement hold true?

Holger
  • 285,553
  • 42
  • 434
  • 765
Prateek Gupta
  • 149
  • 2
  • 11
  • 4
    `forEach` is the exception to the rule… – Holger Jan 05 '18 at 07:04
  • @Holger there is no lambda where you can't alter an object via a reference. – Peter Lawrey Jan 05 '18 at 11:54
  • 5
    @Peter Lawrey: you *can*, but that does not imply that it is a good idea. As [the documentation](https://docs.oracle.com/javase/9/docs/api/java/util/stream/package-summary.html#SideEffects) states: “*Side-effects in behavioral parameters to stream operations are, in general, discouraged, … A small number of stream operations, such as `forEach()` and `peek()`, can operate only via side-effects; these should be used with care.*” – Holger Jan 05 '18 at 12:35
  • 1
    @Holger I have added to my answer to say while you are able to do this, you shouldn't, even for forEach/peek IMHO. – Peter Lawrey Jan 05 '18 at 12:42

3 Answers3

3

The value s isn't being altered in this example, however no deep copy is taken, and there is nothing to stop you altering the object referenced.

Are able to can alter an object via a reference in any context in Java and there isn't anything to prevent it. You can only prevent shallow values being altered.

NOTE: Just because you are able to do this doesn't mean it's a good idea. Altering an object inside a lambda is likely to be dangerous as functional programming models assume you are not altering the data being process (always creating new object instead)

If you are going to alter an object, I suggest you use a loop (non functional style) to minimise confusion.


An example of where using a lambda to alter an object has dire consequences is the following.

map.computeIfAbsent(key, k -> {
    map.computeIfAbsent(key, k -> 1);
    return 2;
});

The behaviour is not deterministic, can result in both key/values being added and for ConcurrentHashMap, this will never return.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • "If you are going to alter an object, I suggest you use a loop (non functional style) to minimise confusion." point noted. Thanks – Prateek Gupta Jan 05 '18 at 20:45
  • Can you explain why it never returns? I believe it is because of some internal synchronization it gets into a deadlock – Thiyagu Jan 07 '18 at 06:26
  • @user7 the lock in CHM is non-reentrant. When it tries to get the lock the second time, it doesn't detect it is already holding it and wait forever for it to be released. – Peter Lawrey Jan 08 '18 at 13:33
2

As mentioned Here

Most importantly, a stream isn’t a data structure.

You can often create a stream from collections to apply a number of functions on a data structure, but a stream itself is not a data structure. That’s so important, I mentioned it twice! A stream can be composed of multiple functions that create a pipeline that data that flows through. This data cannot be mutated. That is to say the original data structure doesn’t change. However the data can be transformed and later stored in another data structure or perhaps consumed by another operation.

AND as per Java docs

This is possible only if we can prevent interference with the data source during the execution of a stream pipeline.

And the reason is :

Modifying a stream's data source during execution of a stream pipeline can cause exceptions, incorrect answers, or nonconformant behavior.


That's all theory, live examples are always good.

So here we go :

Assume we have a List<String> (say :names) and stream of this names.stream(). We can apply .filter(), .reduce(), .map() etc but we can never change the source. Meaning if you try to modify the source (names) you will get an java.util.ConcurrentModificationException .

public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("Joe");
        names.add("Phoebe");
        names.add("Rose");
        names.stream().map((obj)->{
            names.add("Monika"); //modifying the source of stream, i.e. ConcurrentModificationException
             /**
              * If we comment the above line, we are modifying the data(doing upper case)
              * However the original list still holds the lower-case names(source of stream never changes)
              */
            return obj.toUpperCase(); 
        }).forEach(System.out::println);
    }

I hope that would help!

Mehraj Malik
  • 14,872
  • 15
  • 58
  • 85
  • here you have taken List which is a list of primitive type. My question concerns more with List<{Some Class say X}>. Using stream over such a list and then performing an alter operation like setting a field value of X like X.setId() will qualify for modification of the data source being used? – Prateek Gupta Jan 05 '18 at 20:34
0

I understood the part do not modify its underlying data source - as it will not add/remove elements to the source; I think you are safe since you alter an element, you do not remove it.

You ca read comments from Tagir and Brian Goetz here, where they do agree that this is sort of fine.

The more idiomatic way to do what you want, would be a replace all for example:

shapes.replaceAll(x -> {
   if(x.getColor() == BLUE){
       x.setColor(RED);
   }
   return x;
})
Eugene
  • 117,005
  • 15
  • 201
  • 306
  • So is it like adding/deleting comes under the functional behaviour of "modify its underlying data source" ? – Prateek Gupta Jan 05 '18 at 10:47
  • @PrateekGupta exactly – Eugene Jan 05 '18 at 10:48
  • 4
    I don’t think that `shapes.replaceAll(…)` is more idiomatic than `shapes.forEach(…)` (not to confuse with `shapes.stream().forEach(…)`) here. That applies especially to immutable lists which unconditionally throw an exception in `replaceAll`. – Holger Jan 05 '18 at 12:39
  • @Holger So does forEach always returns a new collection object of the result after the operation for both mutable and immutable structure? – Prateek Gupta Jan 05 '18 at 20:41
  • @PrateekGupta `forEach` does not return anything. `setColor` is an action that modifies the element, but not the collection. – Holger Jan 08 '18 at 07:18