Skip to content

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

  1. Use for expensive operations: Delay computation until needed
  2. Be careful with stateful suppliers: They may not be thread-safe
  3. Consider memoization: Cache results for expensive operations
  4. 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
    }
}