How do you combine Predicates in Java using and, or, and negate, and what do these combinators return?
answer
- test(T) -> boolean; and/or/negate build new Predicates
- and uses &&, or uses || — both short-circuit in order
- negate() = NOT; Predicate.not(p) is the static form (Java 11)
- Predicate.not shines for method references (String::isBlank)
- a.and(b).or(c) means (a AND b) OR c
basics
~10 sPredicate is a function returning true/false. p.and(q) is true only if both are true; p.or(q) is true if either is true; p.negate() flips the result. Each returns a new Predicate you can keep combining.
solid answer
~40 sPredicate<T> tests a value and returns a boolean (test(T)). Its default methods compose boolean logic: p.and(q) returns a predicate true when both p and q are true; p.or(q) returns one true when either is true; p.negate() returns the logical NOT. They return new Predicate instances (lazy builders, no mutation), so you can chain p.and(q).or(r). Critically, and and or are short-circuiting in the order written: p.and(q) won't evaluate q if p is false, and p.or(q) won't evaluate q if p is true — matching && and ||. There is also a static Predicate.not(p) (Java 11+) that's handy for negating method references, e.g. filter(Predicate.not(String::isBlank)). These pair naturally with Stream.filter to express compound conditions readably instead of one big boolean expression.
go deeper
Knows and/or/negate combine boolean tests and that each returns a new Predicate to use in filter.
Explains short-circuit semantics, left-associative chaining/grouping, and uses Predicate.not for method references.
Composes small named predicates for readable filters, orders them for short-circuit safety (null-guards first), and knows the lazy/no-mutation builder nature.
Treats predicate composition as a way to express domain rules declaratively and reusably; weighs it against a single expression for clarity and ensures combined predicates remain side-effect-free for safe reuse.
## What a Predicate is `java.util.function.Predicate<T>` is a **functional interface** whose single abstract method is `boolean test(T t)` — it answers a yes/no question about a value. You create one with a lambda (`s -> s.isEmpty()`) or a method reference (`String::isEmpty`). Predicates often need to be **combined**: "is non-null AND non-empty", "is admin OR owner", "is NOT blank". Rather than writing one tangled boolean expression, `Predicate` offers three **default methods** (concrete methods on the interface) that build a new combined `Predicate`. ## and — logical AND ``` default Predicate<T> and(Predicate<? super T> other) { return (t) -> test(t) && other.test(t); } ``` `p.and(q)` returns a new predicate that is `true` only when **both** `p` and `q` return true. Because it uses `&&`, it **short-circuits**: if `p.test(t)` is `false`, `q` is never evaluated. Example: `Predicate<String> nonNull = s -> s != null; Predicate<String> nonEmpty = s -> !s.isEmpty(); nonNull.and(nonEmpty)` safely guards against a null before calling `isEmpty`. ## or — logical OR ``` default Predicate<T> or(Predicate<? super T> other) { return (t) -> test(t) || other.test(t); } ``` `p.or(q)` is `true` when **either** is true, short-circuiting on `||`: if `p.test(t)` is `true`, `q` is skipped. ## negate — logical NOT ``` default Predicate<T> negate() { return (t) -> !test(t); } ``` `p.negate()` returns a predicate that flips the result of `p`. ## Predicate.not (static, Java 11+) ``` static <T> Predicate<T> not(Predicate<? super T> target) { ... } ``` `Predicate.not(p)` is the static equivalent of `p.negate()`. It shines with method references: you can't write `String::isBlank.negate()`, but you can write `Predicate.not(String::isBlank)`. So `list.stream().filter(Predicate.not(String::isBlank))` keeps only non-blank strings. ## Properties - **Lazy builders**: `and`/`or`/`negate` do no testing themselves; they construct a new `Predicate`. The boolean logic runs when you later call `.test(...)`. - **No mutation**: the originals are untouched, so they stay reusable. - **Short-circuit order**: evaluation follows the order you wrote, so put the cheaper or more discriminating test first, and the null-guard before any dereference. - **Chaining + precedence**: chaining is left-associative method calls. `a.and(b).or(c)` means `(a AND b) OR c`. There is no operator-precedence magic — use explicit grouping by ordering your method calls. ## Why it matters These combinators let you assemble compound filters from small, named, independently testable conditions. They read better than a single long boolean and they pair directly with `Stream.filter`, `Collection.removeIf`, and `Optional.filter`.
- Why might you put the null-check predicate before the content-check predicate in p1.and(p2)?Because and short-circuits: if p1 (null-check) is false, p2 is never run, avoiding a NullPointerException when p2 would dereference the value.
- When would you prefer Predicate.not(p) over p.negate()?When p is a method reference you don't have as a variable, e.g. filter(Predicate.not(String::isBlank)); you can't call .negate() directly on a method reference.
saying these in an interview costs you the question
- Saying and/or evaluate both predicates always — they short-circuit like && and ||.
- Thinking negate() mutates the original predicate.
- Assuming a.and(b).or(c) groups as a AND (b OR c) — it is (a AND b) OR c.
- Trying String::isBlank.negate() — you must use Predicate.not(String::isBlank) for a method reference.