Java streams non-interference and side-effects

Viewed 257

Looking at Java java.util.stream package documentation a doubt arose regarding best practices to follow in streams usage. Considering this code:

HashMap<Integer,Integer> map = new HashMap<>();
map.put(1,1);
map.put(2,2);
map.put(3,3);
map.put(4,4);
map.keySet().parallelStream().forEach(key -> {
        if (key == 3) { 
            map.put(3,0);
        }
});
  1. Is code ouput always equals to ([1,1],[2,2],[3,0],[4,4])?
  2. Can map.put(3,0) be considered as a non interfering operation?
  3. Can map.put(3,0) be considered as an accetable side-effect?

In other words, the above code can be considered compliant with the best practices suggested in streams documentation?

3 Answers

Your example definitely violates the non interference requirement:

For most data sources, preventing interference means ensuring that the data source is not modified at all during the execution of the stream pipeline. The notable exception to this are streams whose sources are concurrent collections, which are specifically designed to handle concurrent modification.

Your data source, a HashMap, is not designed to handle concurrent modification and therefore it should not be modified at all during the execution of the stream pipeline.

Therefore the answer to your 2nd and 3rd questions is no.

As for the first question, your specific code may still produce the expected result, since your condition ensures only one thread will ever call map.put(3,0). However, this is still considered an incorrect usage of Streams.

No, no, no.
Avoid side effects.
Sample code compliant with the documentation:

Map<Integer,Integer> updatedMap = map.keySet().parallelStream()
        .filter(e -> e == 3)
        .collect(Collectors.toMap(Function.identity(), e -> 0));
map.putAll(updatedMap);

Compare (a)

map.keySet().parallelStream().forEach(key -> {
        if (key == 3) { 
            map.put(3, 0);
        }
});

(a new entry is added)

with (b)

map.entrySet().parallelStream().forEach(e -> {
        if (e.getKey() == 3) { 
            e.setValue(0);
        }
});

(no Entry object is created, moved. But beware of LinkedHashMap.)

  • (a) Unsafe
  • (b) Safe

    1. Is code ouput always equals to ([1,1],[2,2],[3,0],[4,4])?

      (a) no (b) yes

    2. Can map.put(3, 0) be considered as a non interfering operation?

      (a) no (b) setValue(0) yes

    3. Can map.put(3, 0) be considered as an acceptable side-effect?

      (a) no (b) setValue(0) yes

So (a) is evil and (b) is allright.

Why the mention of the entrySet.setValue?

Actually HashMap.put in the Oracle implementation probably does the same as Entry.setValue. That would require the use of implementation knowledge - ugly.

Whereas Entry.setValue is based on a backing of the original map, and one might infere that just the value field is overwritten. Notice that a LinkedHashMap needs to reorder the entry, and that reordering is again unsafe.

Related