Exercise 6 - Lazy
What is Lazy<T>
Lazy<T> is a class that implements lazy evaluation in Java. Lazy evaluation means a value is computed only when it's actually needed, not when it's defined.
Motivation:
Improves performance by avoiding unnecessary calculations
Handles potentially expensive operations only when required
Supports operations on values that don't exist yet
As we have seen in the lecture, Lazy has two ways
Use Lambda as Delayed Data
Memoization
And the Lazy<T> we build today combines these two ways!
Example
// Without lazy evaluation - calculates immediately even if never used
int result = expensiveCalculation();
// With lazy evaluation - calculation happens only when get() is called
Lazy<Integer> lazyResult = Lazy.of(() -> expensiveCalculation());
// ...later in code...
int value = lazyResult.get(); // calculation happens hereMain methods explanation
Before delving into deeper details, this is the field and constructor for our Lazy<T>
of()
The of() is Lazy<T>'s factory method, it support two ways to create a Lazy.
of(T v): Creates a Lazy with an already-computed valueof(Producer<? extends T> s): Creates a Lazy with a producer for delayed computation
Implementation
Example
Example
get()
This method is the core of lazy evaluation. It checks if the value is already computed (present in the Maybe container). If available, it returns it; otherwise, it calls the producer to compute the value, stores it for future requests, and returns it. This ensures the computation happens exactly once, the first time it's needed.
Implementation
Example
This get() method beautifully combines memoization and lazy evaluation using a lambda expression. And all the following methods that called get() will inherit this laziness!
map(Transformer<? super T, ? extends U> t)
The map method allows transformation of a Lazy value into another type without triggering immediate computation. It creates a new Lazy that, when evaluated, will first get this Lazy's value and then apply the transformation function to it. This maintains laziness throughout the chain of operations.
Implementation
Example
flatMap(Transformer<? super T, ? extends Lazy<? extends U>> t)
The flatMap method is similar to map but works with transformations that themselves return Lazy values. It prevents ending up with nested Lazy structures (like Lazy<Lazy>). When the resulting Lazy is evaluated, it gets this Lazy's value (this.get()), applies the transformation to get another Lazy (t.transform()), and then gets the value from that second Lazy (the second .get()).
Implementation
Example
filter(BooleanCondition<? super T> c)
The filter method returns a new Lazy that, when evaluated, will test the original value against the provided condition. Unlike filters in collections that remove elements, this filter converts the value into a boolean result indicating whether the condition passed. The evaluation of both the original value and the condition is deferred until get() is called.
Implementation
Example
combine(Lazy<? extends S> src, Combiner<? super T, ? super S, ? extends R> c)
The combine method allows two Lazy values to be combined into a new Lazy result using a combining function. When evaluated, it will get both values and then apply the combiner function to produce the result. This is useful for operations that require multiple inputs, like addition, concatenation, or any binary operation, while preserving lazy evaluation for all components.
Implementation
Example
equals()
Compares Lazy objects based on their computed values.
Implementation
Example
Refer back to the Keys of being lazy, this is concise and important!
Lazy List
Use the above Lazy<T> to build a list that supports:
Delayed evaluation when needed
Memoization
We have come up with the following LazyList.java
In our LazyList, there is no need to keep track of current index or retrieve/insert by index etc.
List::indexOf(T arg): returns the index of the first element whose value
equals the argument, determined by the equals method.
Last updated