-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
69: Built-In Functional Interfaces: Function, Predicate, Supplier
I've seen this happen a dozen times with developers moving from traditional imperative Java to a more functional style. They try to build a flexible filtering system, but they end up fighting the compiler for an hour because they're treating every lambda like a generic "chunk of code" rather than a specific type.
public class OrderService {
public void processOrder(Order order, Predicate<Order> validationRule) {
if (validationRule.test(order)) {
System.out.println("Order is valid. Processing...");
} else {
System.out.println("Order failed validation.");
}
}
public static void main(String[] args) {
Order myOrder = new Order(101, 50.00);
OrderService service = new OrderService();
// BUG: This line won't compile
service.processOrder(myOrder, o -> o.getTotalAmount());
}
}
The 'Incompatible Types' Headache
If you try to compile the code above, Java will throw a fit. It'll tell you that it found a double (from getTotalAmount()) but it required a boolean. The mistake here is a conceptual one: the developer wanted to "check the amount," but they passed a transformer (something that extracts a value) into a slot designed for a filter (something that returns true/false).
A Predicate is a very specific contract. It says: "Give me an object, and I will tell you true or false." By passing o -> o.getTotalAmount(), you aren't providing a true/false answer; you're just handing back a number. The compiler doesn't care that you *intend* to use that number for a check later; it only cares that the test() method of a Predicate must return a boolean.
The Fix: Matching the Lambda to the Interface
To fix this, we need to actually perform a comparison within the lambda so that the result is a boolean. This fulfills the Predicate contract.
// FIXED: Now it returns a boolean, satisfying the Predicate interface
service.processOrder(myOrder, o -> o.getTotalAmount() > 20.00);
Now, the lambda reads as: "Take an order o, check if the amount is greater than 20, and return that result." The compiler is happy because the types align.
Filtering Logic with Predicate
As you've seen, Predicate<T> is your go-to for any logic that asks a yes/no question. It has one primary abstract method: boolean test(T t). I use these constantly when I want to make my methods generic. Instead of writing five different methods like filterByPrice(), filterByDate(), and filterByStatus(), I write one method that accepts a Predicate and let the caller decide the logic.
Transforming Data with Function
While a Predicate filters, a Function<T, R> transforms. It takes an input of type T and returns a result of type R. Its main method is R apply(T t).
Think of this as a pipe. You put a User object in, and you get a String (like their email) out. If you're extracting data or converting one object type to another (like a Database Entity to a DTO), this is the interface you want.
// A function that takes an Order and returns its Order ID as a String
Function<Order, String> idExtractor = o -> "ORD-" + o.getId();
String label = idExtractor.apply(myOrder);
Generating Values with Supplier
Finally, we have the Supplier<T>. This is the simplest of the three because it takes absolutely no arguments but returns a value of type T. Its method is T get().
I usually use Suppliers for "lazy initialization" or factory-style logic. If you have an expensive object to create, you don't want to create it until the exact moment it's actually needed. A Supplier wraps that creation logic in a lambda, delaying the execution until you call .get().
// A supplier that provides a new timestamp every time it's called
Supplier<LocalDateTime> now = () -> LocalDateTime.now();
System.out.println(now.get());
Choosing the Right Tool
- Need a boolean? Use
Predicate<T>. - Need to change A into B? Use
Function<T, R>. - Need to create or provide something? Use
Supplier<T>.
📋 Practical Task
Building a Dynamic Product Discount Engine
You are tasked with creating a DiscountManager that can handle various business rules without hard-coding them. You need to implement a system that filters eligible products, calculates a discounted price, and generates a log message for the transaction.
Requirements:
- Create a
Productclass withname(String),price(double), andcategory(String). - Create a method
applyDiscountin yourDiscountManagerclass that takes:- A
Productobject. - A
Predicate<Product>to determine if the product is eligible for a discount. - A
Function<Double, Double>to calculate the new price based on the old price. - A
Supplier<String>to provide a custom log prefix (e.g., "PROMO2023: ").
- A
- The
applyDiscountmethod should:- Check if the product is eligible using the predicate.
- If eligible, use the function to calculate the new price and print:
"[Prefix] Product [Name] discounted to [New Price]". - If not eligible, print:
"[Prefix] Product [Name] is not eligible for discount."
Test Scenario: Apply a 10% discount only to products in the "Electronics" category, using "SALE_EVENT" as the log prefix.
There are no comments for now.