10.4. Supplier with Streams
The Supplier interface works seamlessly with Java Streams to provide lazy evaluation, generate infinite sequences, and create reusable stream sources.
Basic Supplier with Streams
Generating Stream Elements
java
import java.util.function.Supplier;
import java.util.stream.Stream;
public class SupplierWithStreams {
public static void main(String[] args) {
// Supplier for random numbers
Supplier<Double> randomSupplier = Math::random;
// Generate stream of random numbers
Stream<Double> randomStream = Stream.generate(randomSupplier);
// Take first 5 random numbers
randomStream
.limit(5)
.forEach(System.out::println);
}
}
Constant Value Stream
java
import java.util.function.Supplier;
import java.util.stream.Stream;
public class ConstantStream {
public static void main(String[] args) {
// Supplier that always returns the same value
Supplier<String> constantSupplier = () -> "Hello";
// Infinite stream of "Hello"
Stream<String> helloStream = Stream.generate(constantSupplier);
// Take 3 elements
helloStream
.limit(3)
.forEach(System.out::println);
// Output:
// Hello
// Hello
// Hello
}
}
Stateful Suppliers with Streams
Sequential Number Generator
java
import java.util.function.Supplier;
import java.util.stream.Stream;
public class SequentialNumberGenerator {
public static void main(String[] args) {
// Stateful supplier for sequential numbers
Supplier<Integer> sequenceSupplier = new Supplier<Integer>() {
private int count = 0;
@Override
public Integer get() {
return count++;
}
};
// Generate sequence: 0, 1, 2, 3, ...
Stream<Integer> numberStream = Stream.generate(sequenceSupplier);
numberStream
.limit(10)
.forEach(System.out::println);
}
}
Fibonacci Sequence with Supplier
java
import java.util.function.Supplier;
import java.util.stream.Stream;
public class FibonacciSupplier {
public static void main(String[] args) {
// Supplier for Fibonacci sequence
Supplier<Long> fibonacciSupplier = new Supplier<Long>() {
private long previous = 0;
private long current = 1;
@Override
public Long get() {
long next = previous + current;
previous = current;
current = next;
return previous;
}
};
// Generate Fibonacci sequence
Stream<Long> fibonacciStream = Stream.generate(fibonacciSupplier);
fibonacciStream
.limit(10)
.forEach(n -> System.out.print(n + " "));
// Output: 1 1 2 3 5 8 13 21 34 55
}
}
Practical Examples
Object Creation Stream
java
import java.util.function.Supplier;
import java.util.stream.Stream;
import java.util.ArrayList;
import java.util.List;
class User {
private static int nextId = 1;
private int id;
private String name;
public User(String name) {
this.id = nextId++;
this.name = name;
}
@Override
public String toString() {
return "User{id=" + id + ", name='" + name + "'}";
}
}
public class ObjectCreationStream {
public static void main(String[] args) {
// Supplier for creating User objects
Supplier<User> userSupplier = () -> new User("User_" + System.currentTimeMillis());
// Generate stream of users
List<User> users = Stream.generate(userSupplier)
.limit(5)
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
users.forEach(System.out::println);
}
}
Resource Pool with Supplier
java
import java.util.function.Supplier;
import java.util.stream.Stream;
import java.util.ArrayList;
import java.util.List;
class DatabaseConnection {
private static int connectionCount = 0;
private final int id;
public DatabaseConnection() {
this.id = ++connectionCount;
System.out.println("Creating database connection " + id);
}
public void connect() {
System.out.println("Using connection " + id);
}
@Override
public String toString() {
return "Connection#" + id;
}
}
public class ResourcePool {
public static void main(String[] args) {
// Supplier for database connections
Supplier<DatabaseConnection> connectionSupplier = DatabaseConnection::new;
// Create connection pool
List<DatabaseConnection> connectionPool = Stream.generate(connectionSupplier)
.limit(3)
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
System.out.println("Connection pool: " + connectionPool);
// Use connections
connectionPool.forEach(DatabaseConnection::connect);
}
}
Advanced Stream Operations with Supplier
Retry Mechanism with Supplier
java
import java.util.function.Supplier;
import java.util.function.Predicate;
import java.util.Optional;
public class RetryMechanism {
public static <T> Optional<T> retry(Supplier<T> supplier,
Predicate<T> condition,
int maxAttempts) {
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
try {
T result = supplier.get();
if (condition.test(result)) {
System.out.println("Success on attempt " + attempt);
return Optional.of(result);
}
System.out.println("Attempt " + attempt + " failed condition");
} catch (Exception e) {
System.out.println("Attempt " + attempt + " failed with exception: " + e.getMessage());
}
}
return Optional.empty();
}
public static void main(String[] args) {
// Supplier that sometimes fails or returns invalid results
Supplier<Integer> unreliableSupplier = () -> {
double random = Math.random();
if (random < 0.3) {
throw new RuntimeException("Random failure");
}
return (int) (random * 100);
};
// Condition: value must be greater than 50
Predicate<Integer> validCondition = value -> value > 50;
// Retry up to 5 times
Optional<Integer> result = retry(unreliableSupplier, validCondition, 5);
result.ifPresentOrElse(
value -> System.out.println("Final result: " + value),
() -> System.out.println("All attempts failed")
);
}
}
Caching with Supplier
java
import java.util.function.Supplier;
import java.util.HashMap;
import java.util.Map;
public class MemoizationSupplier {
public static <T, R> Supplier<R> memoize(Function<T, R> function, T input) {
Map<T, R> cache = new HashMap<>();
return () -> cache.computeIfAbsent(input, function);
}
public static void main(String[] args) {
// Expensive computation function
Function<Integer, Integer> expensiveComputation = n -> {
System.out.println("Computing result for " + n);
try { Thread.sleep(1000); } catch (InterruptedException e) {}
return n * n;
};
// Memoized supplier
Supplier<Integer> memoizedSupplier = memoize(expensiveComputation, 5);
System.out.println("First call (should compute):");
System.out.println("Result: " + memoizedSupplier.get());
System.out.println("Second call (should use cache):");
System.out.println("Result: " + memoizedSupplier.get());
System.out.println("Third call (should use cache):");
System.out.println("Result: " + memoizedSupplier.get());
}
}
Best Practices
- Use for expensive operations: Delay computation until needed
- Be careful with stateful suppliers: They may not be thread-safe
- Consider memoization: Cache results for expensive operations
- Use with Stream.generate(): For creating custom sequences
java
import java.util.function.Supplier;
import java.util.stream.Stream;
public class SupplierStreamBestPractices {
public static void main(String[] args) {
// Good practice: Stateless supplier for simple values
Supplier<Double> randomSupplier = Math::random;
// Good practice: Stateful supplier with clear purpose
Supplier<Integer> counterSupplier = new Supplier<Integer>() {
private int count = 0;
@Override
public Integer get() { return count++; }
};
// Best practice: Limit infinite streams
Stream.generate(randomSupplier)
.limit(100)
.forEach(System.out::println);
// Use for resource management
Supplier<ExpensiveResource> resourceSupplier = () -> {
System.out.println("Creating expensive resource");
return new ExpensiveResource();
};
// Lazy initialization pattern
class LazyResource {
private Supplier<ExpensiveResource> resourceSupplier =
() -> createExpensiveResource();
private ExpensiveResource resource;
public ExpensiveResource getResource() {
if (resource == null) {
resource = resourceSupplier.get();
}
return resource;
}
private ExpensiveResource createExpensiveResource() {
System.out.println("Actually creating resource");
return new ExpensiveResource();
}
}
LazyResource lazy = new LazyResource();
System.out.println("Resource not created yet");
lazy.getResource(); // Resource created here
lazy.getResource(); // Uses cached resource
}
static class ExpensiveResource {
// Simulate expensive resource
}
}