Introduction
In the last episode, we mastered Consumer, the functional interface for performing actions.
Now, let’s turn our focus to Supplier, the simplest yet most powerful interface for lazy value generation.
If Predicate
decides, Function
transforms, and Consumer
acts, then Supplier
is the source. It provides values on demand, without requiring input.
What Is Supplier?
Here’s the definition:
@FunctionalInterface
public interface Supplier<T> {
T get();
}
- Input: none.
- Output: an object of type T.
- Purpose: generate or supply values, often lazily or repeatedly.
Why Use Supplier?
Traditionally, values are produced eagerly:
String token = UUID.randomUUID().toString();
With Supplier, we can defer execution and encapsulate value creation:
Supplier<String> tokenSupplier = () -> UUID.randomUUID().toString();
System.out.println(tokenSupplier.get()); // A new token each call
This is especially powerful in lazy evaluation, configuration loading, testing, and dependency injection.
Practical Examples
1. Random or Unique Values
Suppliers naturally express randomness or unique generation:
Supplier<Integer> randomInt = () -> new Random().nextInt(100);
System.out.println(randomInt.get()); // e.g., 42
System.out.println(randomInt.get()); // e.g., 17
2. Deferred Initialization
Delay expensive computation until it’s needed:
Supplier<List<User>> heavyQuery = () -> database.fetchAllUsers();
// Nothing happens yet
List<User> users = heavyQuery.get(); // Query executes only here
3. Factories and Object Creation
Suppliers are elegant factories:
Supplier<User> newUser = () -> new User(UUID.randomUUID().toString());
User user1 = newUser.get();
User user2 = newUser.get();
4. Stream Integration
Suppliers work with Stream.generate to produce infinite sequences:
Supplier<String> uuidSupplier = () -> UUID.randomUUID().toString();
List<String> ids = Stream.generate(uuidSupplier)
.limit(3)
.toList();
System.out.println(ids);
// [550e8400-e29b-41d4-a716-446655440000, ...]
Real-World Patterns
Configuration Providers:
Load config lazily when requested.Caching with Lazy Evaluation:
Compute once, then reuse.Dependency Injection:
Pass Suppliers instead of concrete objects for flexible object creation.Testing and Mocking:
Suppliers make it easy to inject fake or mock data.
Best Practices
- Keep Suppliers Pure: Avoid hidden side effects. Suppliers should primarily generate values.
- Name Clearly: Use names like tokenSupplier, userFactory, configProvider.
- Combine with Higher-Order Functions: Suppliers can be passed to methods to delay execution or customize behavior.
Common Pitfalls
- Uncontrolled Infinite Streams: Stream.generate without a limit leads to infinite loops.
- Heavy Logic Inside: Don’t hide complex operations inside Supplier#get(), it should be lightweight and predictable.
- Side Effects: While possible, side-effect-driven suppliers (e.g., network calls) blur responsibilities. Prefer purity.
Functional Analogy
Think of Supplier as a well:
- You don’t provide anything to it.
- Each time you draw (get()), it gives you fresh water (T).
- The well might be deep, infinite, or cached, but it always supplies.
Conclusion
Supplier is the lazy generator of functional Java.
It encapsulates value creation, supports lazy evaluation, and integrates seamlessly with streams, factories, and testing strategies.
By mastering Supplier, you unlock on-demand computation and flexible object creation.
What’s Next
In the next episode, we’ll dive into UnaryOperator, a specialized function for single-input transformations.
Where Function transforms from one type to another, UnaryOperator specializes in transforming within the same type, a cornerstone of functional composition.
Top comments (0)