With knowledge of those methods, you can now write Java code that never again uses the for loop,
making your code post-functional programmer compliant. The ironic part of this zeal for the iterator method
is that you almost never use the iterator method in functional programming. The reason is the return value:
it is void in Java and is equally useless in almost all languages. This means that it is a hard stop on function
building: for the functional programmer, there is nowhere to go from the iterator method. Its usefulness
is therefore dependent entirely on side effects, which is antithetical to the entire functional programming
paradigm. In Listing 3-3, for instance, a functional programmer would map the Book to a String , then
pipe that into String concatenation, then pipe the result into System.out.println . Unfortunately, the
Consumer type prevents that kind of chaining, because it is depending on the side effect of the Consumer
implementation to accomplish anything interesting.
This is one of the places where post-functional languages and functional languages part ways most
strongly. Post-functional languages accept side effects as a reasonable way to accomplish the programmer's
goals, whereas functional languages punish the programmer for trying to code with side effects. While
post-functional languages provide significantly more flexibility to the programmer, they also make it harder
to read the code, reason about the code, or provide guarantees about its behavior (such as thread safety).
It's the classic case of great power bringing great responsibility.
The takeaway lesson for the Java 8 developer is that it is fine to use the forEach / forEachRemaining
methods. However, any time you reach for these methods, consider carefully whether you really want
one of the other comprehensions. Using the right method for the job will improve your code's readability,
give power to the runtime and library developers to optimize it appropriately, and exercise your habit of
programming in a more powerfully functional way.
It is also worth noting that if you want to manipulate the collection, such as adding and removing
members, this is not the way to do it. Attempting to modify the collection from within the forEach method
may result in a ConcurrentModificationException occurring. Since the contract for forEach just says that
the exception may occur, it is safe to assume that the exception will wait to occur until the worst possible
circumstance, and only then bring your code down. If you want to manipulate the collection or map by
passing in a lambda, you will have to use special methods for that.
Manipulating Collections and Maps with Lambdas
Within mathematics, there is no mutability: something that is true is always true; something that is false is
always false. Functional programming follows mathematics' lead on this, and presumes that the domain
cannot be changed. When you use a collection or a map as the domain of a lambda, the result will be a new
collection, map, or object. In this section, we will explore the particular kinds of operations that are available.
Filtering Collections and Maps
When you are working with collections, the most common manipulation that you will want to do is to
remove certain elements. In other post-functional languages (and in Java's Stream API), this is referred to
as “ filter .” When you want to modify the collection by removing the elements directly, the method to use
is Collection.removeIf . The removeIf method takes a Predicate and will remove from the collection all
those elements for which the Predicate tests true . Java does not provide any Map equivalent, but you can
use removeIf on the collections returned by Map.entrySet() , Map.values() , and Map.keySet() in order to
change the underlying Map . The method returns true if there was a change made to the collection but false
if the collection remained unchanged.
In Listing 3-4, we use the removeIf functionality to extend our Library class, providing the ability to
remove all the topics of the given genre. Since we want to perform a test on the result of some processing, we
define a utility method here: mapThenTest . This provides us a predicate that performs some mapping and
tests the result of the mapping: in our case, we want to get the genre and then perform a test of the genre.
With that predicate defined, we then pass it into removeIf for the topics set and the featured books set.