Advanced Use of Predicate, Function, Consumer, and Comparator in Java



This content originally appeared on DEV Community and was authored by William

Suppose you are building a Java application to manage a product inventory. You need to filter products by category, transform them into a formatted report, perform actions like logging, and sort them by price. Traditionally, this requires verbose loops and anonymous classes, leading to code that’s hard to read and maintain. How can we handle this elegantly? Java’s functional interfaces: Predicate, Function, Consumer, and Comparator, combined with lambda expressions and the Stream API, provide a concise, functional solution.

Example Scenario

Imagine a Product class with name, category, and price.

class Product {
    private String name;
    private String category;
    private double price;

    public Product(String name, String category, double price) {
        this.name = name;
        this.category = category;
        this.price = price;
    }

    public String getName() { return name; }
    public String getCategory() { return category; }
    public double getPrice() { return price; }

    @Override
    public String toString() {
        return name + " (" + category + ", $" + price + ")";
    }
}

We want to:

  1. Filter products in the “Electronics” category with a price above $100.
  2. Transform each product into a formatted report string.
  3. Log each processed product to a list.
  4. Sort the results by price in descending order.

Solution

public class ProductDemo {
    public static void main(String[] args) {

        List<Product> products = Arrays.asList(
                new Product("Phone", "Electronics", 499.99),
                new Product("Laptop", "Electronics", 999.99),
                new Product("Desk", "Furniture", 150.00),
                new Product("Headphones", "Electronics", 89.99),
                new Product("Tablet", "Electronics", 299.99)
        );


        Predicate<Product> isExpensiveElectronics = p ->
                p.getCategory().equals("Electronics") && p.getPrice() > 100;

        Function<Product, String> formatReport = p ->
                String.format("Product: %s, Price: $%.2f", p.getName(), p.getPrice());

        List<String> log = new ArrayList<>();
        Consumer<Product> logProduct = p -> log.add("Processed: " + p);

        Comparator<Product> byPriceDesc = (p1, p2) ->
                Double.compare(p2.getPrice(), p1.getPrice());


        List<String> report = products.stream()
                .filter(isExpensiveElectronics)
                .peek(logProduct)
                .sorted(byPriceDesc)
                .map(formatReport)
                .toList();

        System.out.println("Formatted Report:");
        report.forEach(System.out::println);

        System.out.println("\nAudit Log:");
        log.forEach(System.out::println);
    }
}

Explanation of the Code

  • Predicate: isExpensiveElectronicscombines two conditions (category and price) to filter electronics over $100.
  • Function: formatReport transforms each product into a formatted string for the report.
  • Consumer: logProduct adds each filtered product to an audit log using peek.
  • Comparator: byPriceDesc sorts products by price in descending order.
  • Stream Pipeline: Chains filter, peek, sorted, and map to process the list at once.

Output:

Formatted Report:
Product: Laptop, Price: $999.99
Product: Phone, Price: $499.99
Product: Tablet, Price: $299.99

Audit Log:
Processed: Laptop (Electronics, $999.99)
Processed: Phone (Electronics, $499.99)
Processed: Tablet (Electronics, $299.99)

Conclusion

Predicate, Function, Consumer, and Comparator address the challenge of complex data processing by enabling a concise, functional pipeline. In this inventory example, they filtered, transformed, logged, and sorted products efficiently, reducing boilerplate compared to traditional loops and classes. With no external dependencies and tight integration with Java’s Stream API, these interfaces make code modular, readable, and maintainable, solving real-world data processing problems in modern Java applications.


This content originally appeared on DEV Community and was authored by William