In Java, Predicate
, Supplier
, and Consumer
are functional interfaces commonly used in lambda expressions and the Stream API. Each has a distinct purpose and is defined in the java.util.function
package. Here’s a breakdown of each interface, including its main purpose, method signatures, and common use cases.
1. Predicate
- Purpose: Represents a boolean-valued function (test) that takes one argument and returns
true
orfalse
. - Method Signature:
boolean test(T t)
- Common Use Cases: Filtering collections, evaluating conditions, and any scenarios where a yes/no (true/false) decision is needed.
Example:
Predicate isLongerThan5 = s -> s.length() > 5;
System.out.println(isLongerThan5.test("Hello")); // false
System.out.println(isLongerThan5.test("Hello World")); // true
- Combining Predicates: Predicates can be combined using
and()
,or()
, andnegate()
for complex conditions.
Predicate hasLength = s -> s.length() > 5;
Predicate startsWithA = s -> s.startsWith("A");
Predicate combinedPredicate = hasLength.and(startsWithA);
2. Supplier
- Purpose: Represents a function that supplies a value without taking any input (think of it as a “producer”).
- Method Signature:
T get()
- Common Use Cases: Lazy generation of values, factories, or any situation where an instance is required without initial parameters.
Example:
Supplier greetingSupplier = () -> "Hello, World!";
System.out.println(greetingSupplier.get()); // Hello, World!
- Example Use Case: Used with APIs that demand deferred execution, such as
Optional.orElseGet()
where the supplier is only called if theOptional
is empty.
Optional optional = Optional.ofNullable(null);
String result = optional.orElseGet(() -> "Default Value");
System.out.println(result); // Default Value
3. Consumer
- Purpose: Represents an operation that takes a single input argument and returns no result (think of it as an “action” or “consumer”).
- Method Signature:
void accept(T t)
- Common Use Cases: Iterating over collections, performing operations on each element, and any scenario where an action is performed without returning a value.
Example:
Consumer printConsumer = s -> System.out.println(s);
printConsumer.accept("Hello, Consumer!"); // Prints: Hello, Consumer!
- Chaining Consumers: Consumers can be chained with
andThen()
to perform multiple actions sequentially.
Consumer print = System.out::println;
Consumer printUpperCase = s -> System.out.println(s.toUpperCase());
print.andThen(printUpperCase).accept("hello");
// Prints: hello
// Prints: HELLO
Comparison Summary
Interface | Purpose | Input | Output | Method Signature |
---|---|---|---|---|
Predicate | Tests a condition | 1 parameter | boolean | boolean test(T t) |
Supplier | Supplies a value | None | Value | T get() |
Consumer | Consumes a value | 1 parameter | void | void accept(T t) |
Summary and Practical Example
These interfaces are heavily utilized with the Java Streams API:
- Predicate is useful with
filter()
. - Supplier is handy with
Optional
or deferred execution. - Consumer works well with
forEach()
and similar operations where an action needs to be applied.
Here’s an example using all three:
List names = Arrays.asList("Alice", "Bob", "Charlie", "David");
// Predicate to filter names that start with 'A'
Predicate startsWithA = name -> name.startsWith("A");
names.stream().filter(startsWithA).forEach(System.out::println);
// Supplier to provide a default name if list is empty
Supplier defaultName = () -> "Default Name";
String name = names.stream().filter(startsWithA).findFirst().orElseGet(defaultName);
System.out.println(name);
// Consumer to print each name in uppercase
Consumer printUpperCase = s -> System.out.println(s.toUpperCase());
names.forEach(printUpperCase);
This example demonstrates each interface in a real-world scenario, emphasizing how they can complement each other to create expressive, concise, and functional Java code.