Lazy¶
Lazy<A> is a value computed the first time it is read, then cached. Later reads return the cached value without
computing it again.
When to use it¶
Use Lazy for a value that is costly to compute and may never be needed.
A plain Supplier computes its value on every call; a Lazy computes it once. Unlike the other control types, it
has no failure case and is never empty.
Construction and reading¶
Lazy.of takes a Supplier and computes nothing. get() computes the value on the first call. isEvaluated()
tells whether that has happened.
var answer = Lazy.of(() -> 6 * 7); // Lazy<Integer>
var before = answer.isEvaluated();
var value = answer.map(n -> n + 1).get(); // Integer
var after = answer.isEvaluated();
// before is false, value is 43, after is true
Lazy is a class with no cases, so there is nothing to pattern match on: get() is how you read it.
Operations¶
map, flatMap, zip, zipWith and the other combinators return a new Lazy that is not computed yet. The
values they depend on are computed when the result is.
var host = Lazy.of(() -> "localhost"); // Lazy<String>
var port = Lazy.of(() -> 8080); // Lazy<Integer>
var address = host.zipWith(port, (h, p) -> h + ":" + p); // Lazy<String>
var evaluated = host.isEvaluated();
var value = address.get(); // String
// evaluated is false, value is "localhost:8080"
The static collectAll turns many Lazy values into one Lazy of a Vector, and flatten removes one level of
nesting. Both compute nothing until the result is read.
var all = Lazy.collectAll(Vector.of(Lazy.of(() -> 1), Lazy.of(() -> 2))); // Lazy<Vector<Integer>>
// all.get() is Vector(1, 2)
Conversions¶
get() gives the value. toSupplier() passes a Lazy to an API that takes a Supplier; the supplier shares the
cache, so the value is still computed once.
var calls = new int[] {0};
var greeting = Lazy.of(() -> {
calls[0]++;
return "hello";
}); // Lazy<String>
var supplier = greeting.toSupplier(); // java.util.function.Supplier<String>
var twice = supplier.get() + supplier.get();
// twice is "hellohello", calls[0] is 1: computed once
A Lazy may hold null, so wrapping its value in another control type is explicit: Option.ofNullable(lazy.get()).
Sharp edges¶
A failed computation is not cached¶
If the computation throws, the exception reaches the caller of get() and nothing is cached. The next get() runs
the computation again.
var attempts = new java.util.concurrent.atomic.AtomicInteger();
var flaky = Lazy.of(() -> {
if (attempts.incrementAndGet() == 1) {
throw new IllegalStateException("not yet");
}
return "ready";
}); // Lazy<String>
var first = Try.of(flaky::get); // Try<String>
var second = flaky.get(); // String
// first is Failure(java.lang.IllegalStateException: not yet), second is "ready", attempts is 2
equals and hashCode compute the value¶
Two Lazy values are equal when their values are, so equals and hashCode compute them. toString does not: it
prints Lazy(?) until the value is computed.
var unread = Lazy.of(() -> 1); // Lazy<Integer>
var other = Lazy.of(() -> 1); // Lazy<Integer>
var shown = unread.toString();
var equal = unread.equals(other);
// shown is "Lazy(?)", equal is true, and both are now evaluated
tap computes the value¶
tap runs its action on the value right away, so it computes the value if needed. It does not wait for a later
get().