class Lazy<T> implements Supplier<T> {
	private final Supplier<? extends T> supplier;
	private Optional<T> cache; // cannot be final
	
	private Lazy(Supplier<? extends T> supplier) {
		this.supplier = supplier;
		this.cache = Optional.<T>empty();
	}
	
	public T get() {
		return this.cache.orElseGet(() -> caching());
	}
	
	// attempt 1
	private T caching() {
		this.cache = this.supplier.get();
	}
	
	// attempt 2
	private T caching() {
		T v = this.supplier.get(); 
		// calls foo() and prints
		// foo() returns 1 so now v = 1
		
		this.cache = Optional.<T>of(v); // stores 1 in cache
		return v; // returns the same 1 in v
	}

Lazy is observably immutable The internal state does change (the cache field goes from Optional.empty() to Optional.of(v)) BUT from the caller’s perspective, the behaviour is always the same — get() always returns the same value.

Strictly/technically immutable means: All fields are final No setters or methods that modify fields


When I call map, we just create Lazy<R>(() mapper.apply(this.get())) We are just adding a function declaration, that isn’t called yet. “this.get()” isn’t called yet because the lambda isn’t activated.

How it works

  1. lazyint.map(x x + 1) → creates new Lazy wrapping the lambda, nothing runs
  2. .get() is called → now the lambda runs → calls this.get() on lazyint → calls foo() → returns 1 → mapper applies x + 1 → returns 2
  3. lazyint has 1 cached. So this.get() returns the cached value!

An eager implementation

<R> Lazy<R> map(Function<? super T, ? extends R> mapper) {
	R r = mapper.apply(this.get());
	return new Lazy<R>(() -> r);
}

lazy_add

Lazy<Integer> lazy_add(Lazy<Integer> x, Lazy<Integer> y) {
	return x.flatMap(a -> y.flatMap(b -> Lazy.of(() -> foo2(a + b))));
}

or

Lazy<Integer> lazy_add2(Lazy<Integer> x, Lazy<Integer> y) {
	return x.flatMap(a -> y.map(b -> foo2(a + b)));
}

jshell> r2.get()
foo2 method evaluated: 1
foo2 method evaluated: 2
foo2 method evaluated: 3
$.. ==> 3

x, y, and the addition are all unevaluated until .get() is called When .get() finally runs, it evaluates in order: foo2(1) — evaluates x foo2(2) — evaluates y foo2(3) — evaluates the addition

norm_add

Integer norm_add(Integer x, Integer y) {
	return foo2(x + y);
}

jshell> norm_add(foo2(10), foo2(20))
foo2 method evaluated: 10
foo2 method evaluated: 20
foo2 method evaluated: 30
$.. ==> 30

eagerly evaluates both arguments before even entering the function body.

The big idea

flatMap lets you chain lazy computations together while keeping everything deferred until .get()