Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

simulate Lazy in Java8

Tags:

I wrote the following code to simulate Lazy<T> in Java:

import java.util.function.Supplier;

public class Main {

    @FunctionalInterface
    interface Lazy<T> extends Supplier<T> {
        Supplier<T> init();
        public default T get() { return init().get(); }
    }

    static <U> Supplier<U> lazily(Lazy<U> lazy) { return lazy; }
    static <T>Supplier<T> value(T value) { return ()->value; }

    private static Lazy<Thing> thing = lazily(()->thing=value(new Thing()));
    public static void main(String[] args) {

        System.out.println("One");
        Thing t = thing.get();
        System.out.println("Three");

    }
    static class Thing{ Thing(){System.out.println("Two");}}
}

but I get the following warning:

"value (T) in Main cannot be applied to (com.company.Main.Thing) reason: no instance(s) of type variable(s) T exist so that Supplier<T> conforms to Lazy<Thing>"

could you please help me find out what the problem is? thanks in advance!

like image 788
shaked sapir Avatar asked Oct 23 '17 09:10

shaked sapir


2 Answers

Lazy is a subclass of Supplier and you are trying to cast it otherways.

Changing

private static Lazy<Thing> thing = lazily(() -> thing = value(new Thing()));

to

private static Supplier<Thing> thing = lazily(() -> thing = value(new Thing()));

should work.

like image 72
Naman Avatar answered Oct 11 '22 18:10

Naman


value() returns a Supplier, while thing field has type Lazy<Thing>. You cannot assign a Supplier to a Lazy (with whatever parametrization) because not all Supplier instances are Lazy instances.

Further, lazily() return value (which is a Supplier) is tried to be assigned to the thing, and this is not gonna work for the same reason.

We can change lazily type to Lazy and remove that inline thing= assignment (which is inside thing initializer expression) to make it compile:

static <U> Lazy<U> lazily(Lazy<U> lazy) { return lazy; }
static <T> Supplier<T> value(T value) { return ()->value; }

private static Lazy<Thing> thing = lazily(()->value(new Thing()));

But I'm not sure whether this is what you wanted to get.

If you just wanted a lazy behaviour, Supplier itself is already capable of acting lazy as get() is only executed when requested to and not when a Supplier is created.

If you want a caching logic (only compute once, and only compute it needed), you could use something like this:

public class CachingSupplier<T> implements Supplier<T> {
    private final Supplier<T> supplier;
    private T cachedValue;
    private boolean computed = false;

    public CachingSupplier(Supplier<T> supplier) {
        this.supplier = supplier;
    }

    public T get() {
        if (!computed) {
            cachedValue = supplier.get();
            computed = true;
        }
        return cachedValue;
    }
}

If you want to guarantee that supplier.get() gets called at maximum once, you could apply some synchronization:

    if (!computed) {
        synchronized (this) {
            if (!computed) {
                cachedValue = supplier.get();
                computed = true;
            }
        }
    }
    return cachedValue;

Here, a double-checked locking is used.

like image 43
Roman Puchkovskiy Avatar answered Oct 11 '22 17:10

Roman Puchkovskiy