Java Reference
In-Depth Information
Listing 3-10. A Library instance method to count Map.merge
public Map<Book.Genre, Integer> getGenreCounts() {
Map<Book.Genre, Integer> counts = new HashMap<>();
getBooks().forEach(book ->
counts.merge(book.getGenre(), 1, (i, j) -> i + j)
);
return counts;
}
When deciding between Map.compute and Map.merge , the important question is whether your new
value cares about the key, or about the old value. If the new value is based on the key, use Map.compute
(or one of its variants); if the new value cares about the old value, use Map.merge . However, if you forget the
distinction, the type signatures are there to help you out.
Streams
So far, everything we have discussed has been about editing an existing Collection or Map in place.
Throughout the rest of the topic, however, we will focus on the true power of lambdas: functional streams
(hereafter just “streams”). The classes for supporting streams are primarily collected in the new
java.util.stream package, but there are little gems of stream APIs sprinked throughout the rest of the Java
SDK API. The next few chapters will all circle around how those APIs (or their unfortunate lack) will change
the way that you write code with Java 8. Before we can do that, though, we need to get a handle on what a
stream is and how it works.
Streams are like iterators but have explicitly broader capabilities. There was always an assumption with
an interator that you were only iterating through it once, and always on the same thread, and that there was
always an underlying collection that was being iterated over. There were a few attempts to build iterators off
of things other than collections (such as the lines of a file), and even a few attempts to make iterators act
like lazy lists or suppliers. Making that desired functionality fit into the Iterator API always felt like
attempting to shove a square peg into a round hole. Even more, you were always rather limited with what
you could do with an iterator. Unlike I/O streams, for instance, there never developed significant and widely
adopted support for wrapping iterators or manipulating what they returned. An iterator was something that
you used to simply iterate over the elements in a very imperative style, and that was that.
With streams, however, things are entirely different. Streams can be explicitly parallelizable, which
enables the user code and the library itself to read from them and execute them on multiple threads.
Streams are designed to be transformed by Function lambdas, filtered by Predicate lambdas, and generally
manipulated in the functional style that we have grown to love. Even better, they can be collected together
in a variety of ways, enabling them to be plugged into any of the legacy APIs or to efficiently implement
complex processing.
The life cycle of a stream is slightly more complicated than the life cycle of an iterator, because a lot
more is going on. The life cycle of a stream is:
1.
Creation
2.
Mapping and Filtering (called the Intermediate Steps )
3.
Collecting, Processing, or Reducing (called the Terminal Step )
Each of these three steps can take a wide variety of forms, and you can mix and match them like
building blocks to construct practically any flow that you can envision. This is extreme flexibility and
relatively easy to manage, thanks to the type systems. What is even better is that the parallelism of steps 2
and 3 are automatically handled for you by Java, so the Java runtime and libraries themselves automatically
optimize your implementation.
 
Search WWH ::




Custom Search