Suppliers and Lazy Evaluation

Introduction
In Java, a Supplier is a functional interface introduced in Java 8 as part of the functional programming API, as explained in my previous article.
It is an interface that takes no arguments and produces a result of a specific type. Its abstract method is called get().
@FunctionalInterface
public interface Supplier<T> {
T get();
}
Usage:
Supplier<Double> randomSupplier = () -> Math.random();
Double randomValue = randomSupplier.get();
But when is providing a value without taking any input useful?
Factory methods
public interface Shape {
void draw();
}
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a circle");
}
}
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a rectangle");
}
}
Factory pattern using Supplier:
final static Map<String, Supplier<Shape>> map = new HashMap<>();
static {
map.put("circle", () -> new Circle());
map.put("rectangle", () -> new Rectangle());
}
The whole factory class is:
public class ShapeFactory {
final static Map<String, Supplier<Shape>> map = new HashMap<>();
static {
map.put("circle", Circle::new);
map.put("rectangle", Rectangle::new);
}
public Shape getShape(String shapeType) {
Supplier<Shape> shape = map.get(shapeType.toLowerCase());
if (shape != null) {
return shape.get();
}
throw new IllegalArgumentException("Invalid shape type: " + shapeType.toLowerCase());
}
}
The drawback of this technique is that it does not scale well if the factory method getShape needs to take multiple arguments to pass on to the Shape constructors.
Stream API
The generate and iterate methods in the Stream API receive a Supplier as parameter:
Supplier<Double> randomSupplier = () -> Math.random();
Stream.generate(randomSupplier)
.limit(10)
.forEach(System.out::println);
It can be simplified using method references:
Stream.generate(Math::random)
.limit(10)
.forEach(System.out::println);
Lazy initialization
It is a design pattern where the creation of an object or computation of a value is deferred until it is needed. This technique improves performance and reduces memory usage, because it avoids unnecessary computations.
// Eager initialization
String eagerValue = "Eager Initialization";
// Lazy initialization using Supplier
Supplier<String> lazyValueSupplier = () -> "Lazy Initialization";
// Value is not created until get() is called
System.out.println("Before accessing lazy value");
System.out.println(lazyValueSupplier.get()); // Output: Lazy Initialization
The example is useless, but let's imagine a heavy computation task instead of a single String. For this case, it is useful to ensure the object is created only once (memoization).
Let's create a custom wrapper class for this:
public class Lazy<T> {
private final Supplier<T> supplier;
private T value;
public Lazy(Supplier<T> supplier) {
this.supplier = supplier;
}
public T get() {
if (value == null) {
value = supplier.get();
}
return value;
}
}
It is called like that:
Lazy<String> lazyValue = new Lazy<>(() -> "Computed Value");
System.out.println("Before accessing lazy value");
System.out.println(lazyValue.get()); // Output: Computed Value
The syntax for creating a Lazy object in Java is verbose, since it's not a built-in class. In other JVM languages a variable can be declared lazy. For example, in Scala:
lazy val lazyValue = "Computed Value"
Generating an infinite list
This is an application of lazy evaluation. It's a quite common case.
// Generate an infinite stream of random numbers
Stream<Double> infiniteStream = Stream.generate(Math::random);
// Take the first 5 elements and print them
infiniteStream.limit(5).forEach(System.out::println);
// Generate an infinite stream of integers starting from 1
Stream<Integer> infiniteStream = Stream.iterate(1, n -> n + 1);
// Take the first 5 elements and print them
infiniteStream.limit(5).forEach(System.out::println);
Scala's native support for laziness makes it more idiomatic for such tasks:
val infiniteList = LazyList.continually(scala.util.Random.nextDouble())
// Take the first 5 elements and print them
println(infiniteList.take(5).toList)
Lazy Evaluation in Kotlin
// Generate an infinite sequence of integers starting from 1
val infiniteSequence = generateSequence(1) { it + 1 }
// Take the first 5 elements and print them
println(infiniteSequence.take(5).toList())
In general, the sequence function is a builder needed for creating sequences lazily.
val randomNumbers = sequence {
while (true) {
yield(Random.nextDouble())
}
}
// Take the first 5 random numbers and print them
println(randomNumbers.take(5).toList())
Custom extension functions can be defined to create infinite sequences for specific use cases. This allows Kotlin to mimic the functional Scala syntax:
fun Int.toInfiniteSequence(): Sequence<Int> = generateSequence(this) { it + 1 }
fun main() {
// Create an infinite sequence starting from 10
val infiniteSequence = 10.toInfiniteSequence()
// Take the first 5 elements and print them
println(infiniteSequence.take(5).toList())
}
Summary
Suppliers are an important part of functional programming in Java.
They have two main use cases:
To encapsulate the logic of value generation.
To allow separating the definition of how a value is generated from when that value is needed. This technique is called lazy evaluation and it's more easily implemented in Scala and Kotlin.




