Java Reference
In-Depth Information
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.