Please consider the below code snippet.
List<String> list = new ArrayList<String>();
list.add("A");
list.add("B");
list.add("C");
List<String> copyList = new ArrayList<String>();
Consumer<String> consumer = s->copyList.add(s);
list.stream().forEach(consumer);
Since we are using lambda expression, as per functional programming (pure functions) it should only compute the input & provide corresponding output.
But here in the example it is trying to add elements to the list which is neither input nor declared inside the lambda scope.
Is this a good practice, I mean, leading to any side effects?
forEach
would be useless if it didn't produce side-effects, since it has no return value. Hence, whenever you use forEach
you should be expecting side-effects to take place. Therefore there's nothing wrong with your example.
A Consumer<String>
can print the String
, or insert it into some database, or write it into some output file, or store it in some Collection
(as in your example), etc...
From the Stream
Javadoc:
A stream pipeline consists of a source (which might be an array, a collection, a generator function, an I/O channel, etc), zero or more intermediate operations (which transform a stream into another stream, such as Stream.filter(Predicate)), and a terminal operation (which produces a result or side-effect, such as Stream.count() or Stream.forEach(Consumer)).
Besides, if you look at the Javadoc of Consumer
, you'll see that it's expected to have side-effects:
java.util.function.Consumer
Represents an operation that accepts a single input argument and returns no result. Unlike most other functional interfaces, Consumer is expected to operate via side-effects.
I guess this means Java Stream
s and functional interfaces were not designed to be used only for "purely" functional programming.
For a forEach
or even a stream().forEach
it is pretty straightforward. Your example works fine.
Be aware though that if you would do this with other streaming methods, then you could get some surprises: e.g. The following code prints absolutely nothing.
List<String> lst = Arrays.asList("a", "b", "c");
lst.stream().map(
s -> {
System.out.println(s);
return "-";
});
In this case, the stream acts more like a builder which prepares a process but does not execute it yet. It's only when a collect
, count
or find...
method is called that the lambda is executed.
An easy way to spot this, is by looking at the return type of the map
method, which in turn is again a Stream
.
Having said that, I think for your specific example, there are easier alternatives.
List<String> list = Arrays.asList("A", "B", "C");
// this is the base pattern for transforming one list to another.
// the map applies a transformation.
List<String> copyList1 = list.stream().map(e -> e).collect(Collectors.toList());
// if it's a 1-to-1 mapping, then you don't really need the map.
List<String> copyList2 = list.stream().collect(Collectors.toList());
// in essence, you could of course just clone the list without streaming.
List<String> copyList3 = new ArrayList<>(list);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With