Lambda's Domain: Collections,
Maps, and Streams
In the last chapter, we saw how lambdas themselves work, but we explicitly focused on lambdas without
creating a context for their use. We extracted lambdas from their natural environment in order to analyze
them under a microscope. In this chapter, we will begin to see lambdas in the wild.
Specifically, we will see how lambdas can be used with the familiar structures of collections and
maps. However, collections and maps actually limit the power of lambdas. To get at their true power, Java
introduced a new concept using an old word: streams. This chapter will close with an analysis of Java 8's
streams, and how these functional streams allow for new forms of code reuse and expressiveness.
This chapter will be more concrete than the last, and we will stay tied to a particular example. This
particular example will have specific use cases where lambdas will help us out. However, to understand all
of this, we first have to understand where Java 8's lambdas are coming from. This requires us to make a quick
return trip back into the strange world of functional programming languages.
Lambdas and Functional Programming
Java 8's lambdas come out of the world of functional programming. Functional programming models
computer programs based on mathematical functions. In functional programming, lambdas represent the
functions themselves. But, every mathematical function acts over a particular set of possible inputs, and
produces a particular set of possible outputs. The inputs are called the “domain of the function,” and the
outputs are called the “range of the function.”
Abstractly, the types of the lambda represent the function's domain and range. The Java type of the
Function interface is Function<T,U>: T is its domain, and U is its range. This is why types are so useful in
the world of functional programming: types allow you to clearly communicate the definition of the function.
For the developers reading the API, types allow them to succinctly describe what you expect to receive and
what you will return. The compiler also reads these types, and it reasons about your code using those types,
providing optimizations not otherwise available.
However, abstractions only take you so far. Sooner or later, you have to execute your code against real
data. You might build up the most beautiful chain of function applications ever conceived, but they won't
accomplish anything for you until you apply them to something. In the functional programming approach,
the goal of your application is simple: collect up all the domain objects that you care about and feed them
through the function. So what kind of thing provides those domain objects for the functions to operate upon?
If you talk to your average Java developer about collecting up objects, he will reach to the Collection
class and its related APIs. Historically, this has been how functional Java code has been written: you take
a collection, apply a function to each element, and return the resulting collection. A function that acts on