Predicate & Its Combinators
Predicate & Its Combinators
A Predicate<T> is a functional interface in java.util.function that represents a single test: given a value of type T, return true or false. It is the standard way to express a boolean-valued condition in Java's functional API — used heavily by the Streams API, Collection methods like removeIf, and any API that needs a filter.
The single abstract method is boolean test(T t). Everything else on Predicate — and, or, negate, not — are default or static methods that let you build compound conditions without writing nested if blocks.
Why composable predicates matter
Imagine you are filtering a list of users. Without composition you either write one giant lambda that does everything, or you write multiple for loops. With composition you define small, named predicates and wire them together — each piece is testable on its own, and the final combination reads like a sentence.
negate — flipping the result
negate() returns a new Predicate that is the logical NOT of the original.
There is also a static helper, Predicate.not(predicate), introduced in Java 11. It is especially handy inside method chains where calling .negate() would require extra parentheses:
and — both conditions must hold
and(other) returns a new Predicate that short-circuits: if the first predicate returns false, the second is never evaluated — exactly like the && operator.
and stops at the first false result, and or stops at the first true result. This mirrors Java's && and || operators — order matters when a predicate has a side-effect or is expensive to evaluate.
or — at least one condition must hold
or(other) returns a Predicate that is true when either side is true.
Combining all three
The real power appears when you chain multiple combinators. Here is a realistic filtering scenario for a list of product prices:
atLeastFive) makes the chain self-documenting. Avoid writing one deeply-nested lambda — it is harder to read and impossible to reuse.
Using Predicate with removeIf
Collection.removeIf(Predicate) is a convenient method that removes every element that satisfies the given predicate. It accepts any Predicate<T>, including a composed one:
removeIf is safe — it was designed for in-place removal. Using stream().filter(...) does not modify the original list; it produces a new stream. Know which behaviour you need before choosing between the two.
Summary
Predicate<T> turns a boolean condition into a first-class object. Its four key operations are:
test(t)— evaluate the condition.negate()/Predicate.not(...)— logical NOT.and(other)— logical AND with short-circuit.or(other)— logical OR with short-circuit.
Building complex filters from small, named predicates is idiomatic Java 8+ style — it is composable, readable, and testable. You will see this pattern constantly when we reach the Streams lesson.