Understand the functional style
When using streams, the
Stream.forEach
method can be a problem for beginners.
Many introductory examples use forEach
to print the result of a computation.
This is understandable, but don't be misled into thinking that forEach
is commonly used as a terminal operation.
It's not.
The problem is that forEach
exists in order to create side-effects, and
side-effects are not in the spirit of purely-functional programming.
In practice, instead of using Stream.forEach
, you'll usually want to return a Collection
,
or compute a final answer.
This is called a reduction operation.
Example
import java.util.LinkedHashSet; import java.util.Set; import static java.util.stream.Collectors.*; import java.util.stream.Stream; /** @since Java 8 */ public class ForEachSideEffects { /** Stream.forEach can only act upon each item in the stream. It must necessarily do an action of some sort with each item in the Stream. That is, forEach can only be used by invoking side-effects. */ void example(){ Stream.of(Atom.values()) .filter(Atom::isAlwaysUnstable) //side-effect: print the item to the console: .forEach(System.out::println) ; } /** Inappropriate! This is not in the spirit of functional programming! This can lead to subtle problems. */ void badExample(){ //defined completely outside the Stream code Set<Atom> unstables = new LinkedHashSet<>(); //external state Stream.of(Atom.values()) .filter(Atom::isAlwaysUnstable) //side-effect: changing external state (the Set)!: .forEach(atom -> unstables.add(atom)); ; } /** Instead of using forEach, with its attendant side-effects, consider 'reducing' the Stream to some result. Here, the Stream is reduced to a Set. */ void avoidForEach(){ //use the stream code to 'reduce' the data to a Set, with no side effects Set<Atom> unstables = Stream.of(Atom.values()) .filter(Atom::isAlwaysUnstable) //no side-effect: .collect(toSet()) //static import of Collectors ; //use the Set here for(Atom unstable : unstables){ //...do something } } /** Some data from the periodic table of the elements (incomplete!). */ enum Atom { H("Hydrogen", 1, false), Tc("Technetium", 43, true), Au("Gold", 79, false), Pb("Lead", 82, false), Pu("Plutonium", 94, true), Fm("Fermium", 100, true); private Atom(String name, Integer numProtons, Boolean isAlwaysUnstable){ this.name = name; this.numProtons = numProtons; this.isAlwaysUnstable = isAlwaysUnstable; } Boolean isAlwaysUnstable() { return isAlwaysUnstable; } String getName() { return name; } Integer getNumProtons() { return numProtons; } private String name; private Integer numProtons; private Boolean isAlwaysUnstable; } }