Predicate.and and Predicate.or
Predicates are responsible for supplying Boolean checks, and those are usually composite kinds of
structures. Although we opened the topic talking about a simple case (a “not null” predicate), you are rarely
so lucky as to have a simple predicate logic. Usually, you have complex chains of logic. In fact, one of the
major advantages of using the predicate class is that you can construct this complex chain at a high level,
and then execute it upon objects later. To join two predicates, you have to decide if you are joining them
using an && -style join or a || -style join. The former case is handled by Predicate. and the latter case by
In both these cases, the predicate is short circuiting: a predicate won't be executed if we already know the
answer. If you have foo.and(bar).test(baz) , and foo.test(bar) evaluates to false , then bar.test(baz)
is never executed. Similarly, if you have foo.or(bar).test(baz) , and foo.test(bar) evaluates to true , then
bar.test(baz) is never executed. This is exactly how Java works with its Boolean operators.
If you have some specific value, and you want a test that will tell you if other values are equivalent to it, then
use Predicate.isEqual . It uses Java's own Objects.equals(left,right) method, which first performs null
checks and then returns left.equals(right) , and so Predicate.isEqual can safely be used with null .
If you want the logical opposite of a given predicate, you can use Predicate.negate to get one. Combining
Predicate.negate with Predicate.isEqual gives you the not-null check that started off Chapter 1:
BinaryOperator.minBy and BinaryOperator.maxBy
These are two very useful methods, but many people do not realize they are there, since these methods
are hidden out of the way among the functional interface types. Both of these methods provide a
BinaryOperator , which is a BiFunction whose arguments and return value are all the same. Since
BinaryOperator extends BiFunction , you can pass in these BinaryOperators any time your BiFunction
type signature matches. The BinaryOperator that is supplied takes in two arguments of the same type, and
returns the argument that is largest ( maxBy ) or smallest ( minBy ).
This may not seem like a big deal, but it very much is. In the next chapter, we will see how this works
with collections and streams of objects, and how these methods fit naturally in. Even better, since these
methods both take a Comparator as an argument, we can hook into all the useful utility methods provided on
Comparator , which will also be covered in the next chapter.
The obvious use case is finding the smallest and largest element among many. However, you can
use that Comparator to pick out the element that is closest to the value you want. You can also use the
Comparator to provide a scoring algorithm, and then use this BinaryOperator to grab elements with the
largest or smallest score. Any time that you have a proper ordering or a concept of “distance from a target,”
BinaryOperator.minBy and BinaryOperator.maxBy will probably help you out.