Java Reference
In-Depth Information
a collection this way is called a “comprehension.” Java 8 makes comprehensions possible, and we will go
through the APIs enabling comprehensions in this chapter. This approach works very well for relatively small
collections of known items, and it is a very natural evolution for a Java developer.
There are, however, some serious limitations. The most obvious limitation is that the Collection API
presumes to have all the data in memory, or at least readily at hand. This may not be the case if you are
acting on all the rows in a database or all the words in a file. A more subtle consequence is that chaining
together a series of these operations creates a series of intermediary collections, each of roughly the same
size, and that is simply wasteful. Finally, most of the Collection API is not thread safe, or has only limited
thread safety characteristics, which hamstrings the concurrent capabilities of lambdas.
To solve this, Java 8 introduced a concept of “functional streams.” The term “stream” already has an
established usage in Java, and functional streams are a new application of this old concept. Traditionally,
the term “stream” refers to Java's I/O world, where we have an I/O stream representing a sequence of bytes.
Most Java developers are used to working with these streams, and know how their bytes can be wrapped and
contorted and manipulated through the decorator pattern, all while staying within the InputStream API.
This is a powerful way to gain significant code reuse.
Functional streams are like these I/O streams, but they act on a sequence of objects instead of a sequence
of bytes. Objects can be wrapped and contorted and manipulated as they pass through the stream, and we
can gain the same kind of reuse. Thanks to the introduction of lambdas, we don't need the decorator pattern
anymore: we can simply pass our implementations into the Stream object itself and get a new Stream back.
Functional streams work better than the collection approach because you don't need all the data in
a collection up front, and the Java runtime is free to automatically leverage opportunities for concurrent
execution when it can. The resulting API is extremely powerful, although it can be somewhat intimidating
for Java developers: it is coming from a different world, so it may be unintuitive at the start. The rest of this
chapter is about making streams make sense; by the time we are finished, they should be as intuitive for you
to use as the Collection and Iterator classes.
To get there, let's start back with the Collection interface that is already familiar. Unlike the previous
chapter, where we discussed things in the abstract, let's set a more concrete example. Let's say that we have
a Library class. Instances of this class store Book objects in an arbitrary but significant order; the user of the
class sets the order of the topics, and we don't want to mess with that. Based on that requirement, we store
a List<Book> instance in each Library instance. In addition, the Library has to provide functionality for
“featured topics.” Some topics are specially featured optionally with a particular feature message. To provide
that functionality, we store a Map<Book,String> instance in each Library instance.
The starting code for Library and Book is given in Listing 3-1. Throughout the rest of this chapter, we
will build out functionality for our Library class using the new functional tools provided in Java 8.
Listing 3-1. The Library and Book classes
Import java.util.Objects;
/**
* Class representing an element of a {@link Library}.
*/
public class Book {
/**
* Represents the allowed genres of a book.
*/
public static enum Genre {
HORROR, COMEDY, TECHNICAL;
}
 
Search WWH ::




Custom Search