Definition
- Predicate
is a functional interface with a single test(T)method returning boolean. - .and(other) composes two predicates into one that is true only when both are true.
- Short-circuits: if the first predicate is false, the second is never evaluated.
- .or(other) returns true if either passes; .negate() inverts the result.
- Named predicates are reusable, testable building blocks.
Priya at the National Bank of India wants employees who both earn above fifty thousand and work in IT. She writes two simple checks and glues them with 'and', so only people passing both checks are selected.
Understanding the Concepts
How do you write and combine predicates?
Define each predicate as a named variable, then compose with .and().
package com.bank.hr;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class PredicateComposition {
public static void main(String[] args) {
List<Employee> employees = List.of(
new Employee("Rajesh", "Loans", 75000.0),
new Employee("Priya", "IT", 82000.0),
new Employee("Amit", "IT", 45000.0),
new Employee("Kavita", "IT", 91000.0),
new Employee("Neha", "Finance", 78000.0)
);
Predicate<Employee> highSalary = e -> e.getSalaryAsDouble() > 50_000;
Predicate<Employee> inIT = e -> "IT".equals(e.getDepartment());
List<Employee> result = employees.stream()
.filter(highSalary.and(inIT))
.collect(Collectors.toList());
result.forEach(e -> System.out.println(
e.getName() + " (" + e.getDepartment()
+ ", " + e.getSalaryAsDouble() + ")"));
}
}
Priya (IT, 82000.0)
Kavita (IT, 91000.0)
Amit is in IT but earns 45000, so he fails the highSalary predicate and is excluded.
What composition methods does Predicate offer?
| Method | Behaviour | Example |
|---|---|---|
| .and(other) | Both must pass | highSalary.and(inIT) |
| .or(other) | Either must pass | highSalary.or(inIT) |
| .negate() | Inverts result | highSalary.negate() |
| Predicate.not(p) | Static negation (Java 11) | Predicate.not(highSalary) |
// Employees who are NOT in IT
List<Employee> notIT = employees.stream()
.filter(inIT.negate())
.collect(Collectors.toList());
// Employees who earn high OR are in IT
List<Employee> either = employees.stream()
.filter(highSalary.or(inIT))
.collect(Collectors.toList());
Why does predicate order matter?
.and() tests the receiver predicate first and short-circuits. Put the cheaper or more selective predicate first.
// If highSalary eliminates 80% of employees,
// putting it first avoids evaluating inIT for those 80%.
filter(highSalary.and(inIT)) // fewer evaluations of inIT
// vs
filter(inIT.and(highSalary)) // evaluates highSalary only for IT employees
What are common mistakes?
Mistake: writing one big boolean expression instead of composable predicates.
// WRONG: not reusable, hard to test individually
.filter(e -> e.getSalaryAsDouble() > 50_000
&& "IT".equals(e.getDepartment()))
// CORRECT: named, reusable, testable
Predicate<Employee> highSalary = e -> e.getSalaryAsDouble() > 50_000;
Predicate<Employee> inIT = e -> "IT".equals(e.getDepartment());
.filter(highSalary.and(inIT))
What should a senior engineer know?
- Each simple predicate has a clear name, can be unit-tested independently, and can be reused across reports.
- Composition turns filtering rules into named, swappable building blocks, which is far more maintainable than giant boolean expressions.
- .and() returns a new Predicate instance; the original predicates are not modified.
- In a filter chain, multiple .filter() calls are equivalent to a single .filter(a.and(b)), but named composition is more readable.
What are common interview follow-up questions?
- "How do you negate a predicate?" Use .negate() or Predicate.not() (Java 11+).
- "Can you compose more than two predicates?" Yes: a.and(b).and(c).
- "What is Predicate.isEqual(target)?" A static factory that creates a predicate testing equality to a given target.
Visual Overview
Predicate composition:
highSalary: salary > 50000
inIT: department == "IT"
highSalary.and(inIT):
┌────────────┐ ┌────────────┐
│ highSalary │─AND─│ inIT │
└────────────┘ └────────────┘
│ │
Rajesh: 75K > 50K? YES Loans? NO → SKIP (short-circuit)
Priya: 82K > 50K? YES IT? YES → KEEP
Amit: 45K > 50K? NO → SKIP (short-circuit)
Kavita: 91K > 50K? YES IT? YES → KEEP
Neha: 78K > 50K? YES Finance? NO → SKIP
Key Takeaway
A Predicate wraps a boolean test, and .and() composes two predicates into one that is true only when both pass. Define each condition as a named predicate, then compose them for clear, reusable filter logic.
.and() short-circuits: if the first predicate fails, the second is never evaluated. Put the more selective or cheaper predicate first for performance. Predicate also offers .or() and .negate() for complete boolean composition.
At the National Bank of India, named predicates like highSalary and inIT are reusable building blocks that can be tested independently and swapped across reports. This is far cleaner than one big boolean expression inside a filter.
Remember one sentence: highSalary.and(inIT) composes two named predicates with short-circuit evaluation, making filters reusable and testable.
---