Stream.forEachOrdered method. This method provides the same functionality as forEach, but with the
added contract that the elements are processed in the order the stream generates them (called “encounter
order”). In general, if you are reaching for Stream.forEach (and especially if you are reaching for Stream.
forEachOrdered ), you should take a long breath and reconsider if there is a better way for you to be handling
the situation, such as a custom Collector . Programming by side effects is almost never the right answer, and
is always a long-term hindrance for maintenance.
The last common terminal operation is called reducing , and it is an initially strange maneuver coming
from functional programming. 2 The idea is that you pass in some initial value, and a function takes an
element and returns an element with the same type as the initial value. The result of that function is passed
along to the next element. The results of these executions are daisy-chained until the entire stream is
consumed, and then the final result is returned. This chained execution is extremely powerful, and once you
get the knack of it, it becomes easy to implement very complicated operations with very simple code. If you
wanted to roll your own string concatenation, for instance, you could implement your reduction this way:
String booksString =
.map(str -> str + "\n")
Conceptually, what this will do is generate a stream of books, convert each book to its string
representation, append a carriage return to each of those string representations, and then reduce that
resulting stream. The reduction step will start with an empty string and concatenate the results. The first
concatenation will concatenate the empty string with the first book; the next concatenation will take the
result from the first and concatenate the second book; the next concatenation will take the result from the
second and concatenate the third book; and it will go on like this until there are no more books, finally
returning the result.
This warrants a word to the wise. The reduce operation is an extremely powerful operation, and it will
almost certainly become a Golden Hammer 3 for you at some point; as you become more proficient with it,
you will start reaching for reduce to terminate all your strings. This is not a bad impulse, because it shows
that you are thinking in a functional way. If, however, you start wanting to pass in more state to your reduce
operation than simply the previous result, then what you really want is a custom Collector instance or a
wrapped Collector instance (c.f. Collectors.collectingAndThen ). Keep this in mind, and you will keep
your code readable and functional while also producing more reusable code.
Before we leave this introduction to streams, it is important to know that there are specific streams for
the int , long , and double primitive types. You generate these streams by using Stream class methods:
mapToInt / flatMapToInt , mapToLong / flatMapToLong , mapToDouble / flatMapToDouble . As you might expect,
they provide some performance overhead compared to passing around object wrappers, although it's
generally not enough to be significant for application code. More importantly, they provide APIs that fit
the shape of many existing methods. For instance, IntStream has a method called mapToObj that takes a
function that maps an int to an Object . The shape of that function perfectly fits the method List.get(int) ,
so you can call intStream.map(List::get) .
2 While talking to functional programmers, they may also refer to the reduce operation as inject or fold .
3 The term “Golden Hammer” comes from the popular quip: “If all you have is a hammer, everything looks like a nail.”
If you only have a hammer, and you are really excited about having it, then what you have is a Golden Hammer.