or of your domain class. This is really simple and suitable if your domain class is a compos-
ite containing different collections.
Of course, if your domain class isn't just a composite and needs to perform some calculation
based on the existing data, then that isn't a suitable route. Even in this situation, though, you
don't necessarily need to build up a custom collector. You can use the reducing collector,
which gives us a generic implementation of the reduction operation over streams.
Example 5-30 shows how we might write our String -processing example using the redu-
Example 5-30. Reducing is a convenient way of making custom collectors
String result =
artists . stream ()
. map ( Artist: : getName )
. collect ( Collectors . reducing (
new StringCombiner ( ", " , "[" , "]" ),
name -> new
new StringCombiner ( ", " , "[" , "]" ). add ( name ),
StringCombiner: : merge ))
. toString ();
is what you might expect given the name. The key difference is the second argument to Col-
lectors.reducing ; we are creating a dedicated StringCombiner for each element in the
stream. If you are shocked or disgusted at this, you should be! This is highly inefficient and
one of the reasons why I chose to write a custom collector.
The introduction of lambda expressions has also enabled other collection methods to be in-
troduced. Let's have a look at some useful changes that have been made to Map .
A common requirement when building up a Map is to compute a value for a given key. A
classic example of this is when implementing a cache. The traditional idiom is to try and re-
trieve a value from the Map and then create it, if it's not already there.
If we defined our cache as Map<String, Artist> artistCache and were wanting to look
up artists using an expensive database operation, we might write something like
Example 5-31. Caching a value using an explicit null check