Usages of Predicate/Producer/Consumer Interfaces in Java

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 or false.
  • 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(), and negate() 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 the Optional 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

InterfacePurposeInputOutputMethod Signature
PredicateTests a condition1 parameterbooleanboolean test(T t)
SupplierSupplies a valueNoneValueT get()
ConsumerConsumes a value1 parametervoidvoid 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.

Related Posts

Leave a Reply

Your email address will not be published. Required fields are marked *