Skip to content

10.1. Supplier Interface

The Supplier interface is a functional interface in Java that represents a supplier of results. It has a single method get() that returns a value without taking any input parameters.

Supplier Interface Definition

java
@FunctionalInterface
public interface Supplier<T> {
    T get();
}

Key Characteristics

  • No input parameters: The get() method takes no arguments
  • Returns a value: Produces a result of specified type
  • Lazy evaluation: Computation happens only when get() is called
  • Functional interface: Can be used with lambda expressions and method references

Basic Supplier Examples

Simple Value Supplier

java
import java.util.function.Supplier;

public class BasicSupplier {
    public static void main(String[] args) {
        // Supplier that provides a constant string
        Supplier<String> messageSupplier = () -> "Hello, World!";
        System.out.println(messageSupplier.get());  // Output: Hello, World!

        // Supplier that provides current timestamp
        Supplier<Long> timestampSupplier = () -> System.currentTimeMillis();
        System.out.println("Current time: " + timestampSupplier.get());
    }
}

Object Creation Supplier

java
import java.util.function.Supplier;
import java.util.ArrayList;
import java.util.List;

public class ObjectSupplier {
    public static void main(String[] args) {
        // Supplier for new ArrayList
        Supplier<List<String>> listSupplier = () -> new ArrayList<>();

        List<String> names = listSupplier.get();
        names.add("Alice");
        names.add("Bob");
        System.out.println("List: " + names);  // Output: List: [Alice, Bob]

        // Supplier for new StringBuilder
        Supplier<StringBuilder> stringBuilderSupplier = StringBuilder::new;
        StringBuilder sb = stringBuilderSupplier.get();
        sb.append("Hello");
        System.out.println(sb.toString());  // Output: Hello
    }
}

Supplier with State

Suppliers can maintain state through captured variables:

java
import java.util.function.Supplier;

public class StatefulSupplier {
    public static void main(String[] args) {
        // Supplier with counter state
        class CounterSupplier implements Supplier<Integer> {
            private int count = 0;

            @Override
            public Integer get() {
                return ++count;
            }
        }

        CounterSupplier counter = new CounterSupplier();
        System.out.println(counter.get());  // Output: 1
        System.out.println(counter.get());  // Output: 2
        System.out.println(counter.get());  // Output: 3
    }
}

Common Use Cases

1. Default Value Provider

java
import java.util.function.Supplier;

public class DefaultValueSupplier {
    public static <T> T getOrDefault(T value, Supplier<T> defaultValueSupplier) {
        return (value != null) ? value : defaultValueSupplier.get();
    }

    public static void main(String[] args) {
        String name = null;

        // Provide default value using Supplier
        String result = getOrDefault(name, () -> "Unknown User");
        System.out.println("Name: " + result);  // Output: Name: Unknown User

        // With non-null value
        String actualName = "Alice";
        String result2 = getOrDefault(actualName, () -> "Unknown User");
        System.out.println("Name: " + result2);  // Output: Name: Alice
    }
}

2. Lazy Initialization

java
import java.util.function.Supplier;

public class LazyInitialization {
    private static class HeavyObject {
        public HeavyObject() {
            System.out.println("HeavyObject created - expensive operation!");
            // Simulate expensive initialization
            try { Thread.sleep(1000); } catch (InterruptedException e) {}
        }

        public void use() {
            System.out.println("Using HeavyObject");
        }
    }

    public static void main(String[] args) {
        // Lazy initialization using Supplier
        Supplier<HeavyObject> heavyObjectSupplier = () -> {
            System.out.println("Creating HeavyObject...");
            return new HeavyObject();
        };

        System.out.println("Program started");
        System.out.println("HeavyObject not created yet");

        // Object created only when needed
        HeavyObject obj = heavyObjectSupplier.get();
        obj.use();
    }
}

3. Configuration Supplier

java
import java.util.function.Supplier;
import java.util.Properties;

public class ConfigurationSupplier {
    private static Properties config = new Properties();

    static {
        config.setProperty("app.name", "MyApplication");
        config.setProperty("app.version", "1.0.0");
        config.setProperty("database.url", "jdbc:mysql://localhost:3306/mydb");
    }

    public static void main(String[] args) {
        // Suppliers for configuration values
        Supplier<String> appNameSupplier = () -> config.getProperty("app.name");
        Supplier<String> dbUrlSupplier = () -> config.getProperty("database.url");
        Supplier<String> missingConfigSupplier = () -> config.getProperty("missing.key", "default");

        System.out.println("App Name: " + appNameSupplier.get());
        System.out.println("DB URL: " + dbUrlSupplier.get());
        System.out.println("Missing Key: " + missingConfigSupplier.get());
    }
}

Specialized Suppliers

Java provides specialized Supplier interfaces for primitive types:

BooleanSupplier

java
import java.util.function.BooleanSupplier;

public class BooleanSupplierExample {
    public static void main(String[] args) {
        BooleanSupplier isEven = () -> System.currentTimeMillis() % 2 == 0;
        BooleanSupplier isAdmin = () -> "admin".equals(System.getProperty("user.role"));

        System.out.println("Is even timestamp: " + isEven.getAsBoolean());
        System.out.println("Is admin: " + isAdmin.getAsBoolean());
    }
}

IntSupplier, LongSupplier, DoubleSupplier

java
import java.util.function.IntSupplier;
import java.util.function.LongSupplier;
import java.util.function.DoubleSupplier;

public class PrimitiveSuppliers {
    public static void main(String[] args) {
        IntSupplier randomInt = () -> (int) (Math.random() * 100);
        LongSupplier currentTime = System::currentTimeMillis;
        DoubleSupplier randomDouble = Math::random;

        System.out.println("Random int: " + randomInt.getAsInt());
        System.out.println("Current time: " + currentTime.getAsLong());
        System.out.println("Random double: " + randomDouble.getAsDouble());
    }
}

Best Practices

  1. Use for lazy evaluation: Delay expensive computations until needed
  2. Prefer method references: Use ClassName::new for object creation
  3. Maintain immutability: Suppliers should be stateless when possible
  4. Handle exceptions: Wrap checked exceptions in runtime exceptions
java
import java.util.function.Supplier;

public class SafeSupplier {
    public static <T> Supplier<T> wrap(Supplier<T> supplier, T defaultValue) {
        return () -> {
            try {
                return supplier.get();
            } catch (Exception e) {
                System.err.println("Error in supplier: " + e.getMessage());
                return defaultValue;
            }
        };
    }

    public static void main(String[] args) {
        Supplier<String> unsafeSupplier = () -> {
            if (Math.random() > 0.5) {
                throw new RuntimeException("Random failure");
            }
            return "Success";
        };

        Supplier<String> safeSupplier = wrap(unsafeSupplier, "Default Value");

        for (int i = 0; i < 5; i++) {
            System.out.println("Result: " + safeSupplier.get());
        }
    }
}