Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does Java 8 have cached support for suppliers?

Tags:

java

java-8

guava

The guava library has it's own Supplier which does not extend Java 8 Supplier. Also guava provides a cache for suppliers - Suppliers#memoize.

Is there something similar, but for Java 8 Suppliers?

like image 635
Cherry Avatar asked Feb 11 '16 05:02

Cherry


People also ask

What is supplier interface in Java 8?

The Supplier Interface is a part of the java. util. function package which has been introduced since Java 8, to implement functional programming in Java. It represents a function which does not take in any argument but produces a value of type T.

What does Suppliers Memoize do?

Suppliers. memoize is a simple method that takes a Supplier and returns a new Supplier that caches the value returned from the supplied Supplier. get() method. This convenient helper allows us to turn any Supplier into a lazily loaded cached value.

What is a supplier class Java?

In Java 8, Supplier is a simple functional interface, which is represents an operator that provides a value for each call. Supplier has only one get() method and has no default method. Java Functional Interface.

Which function method is described by supplier interface?

Java 8 Supplier is a functional interface whose functional method is get(). The Supplier interface represents an operation that takes no argument and returns a result. As this is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference.


2 Answers

There's no built-in Java function for memoization, though it's not very hard to implement it, for example, like this:

public static <T> Supplier<T> memoize(Supplier<T> delegate) {     AtomicReference<T> value = new AtomicReference<>();     return () -> {         T val = value.get();         if (val == null) {             val = value.updateAndGet(cur -> cur == null ?                      Objects.requireNonNull(delegate.get()) : cur);         }         return val;     }; } 

Note that different implementation approaches exist. The above implementation may call the delegate several times if the memoized supplier requested simultaneously several times from the different threads. Sometimes such implementation is preferred over the explicit synchronization with lock. If lock is preferred, then DCL could be used:

public static <T> Supplier<T> memoizeLock(Supplier<T> delegate) {     AtomicReference<T> value = new AtomicReference<>();     return () -> {         T val = value.get();         if (val == null) {             synchronized(value) {                 val = value.get();                 if (val == null) {                     val = Objects.requireNonNull(delegate.get());                     value.set(val);                 }             }         }         return val;     }; } 

Also note, as @LouisWasserman correctly mentioned in comments, you can easily transform JDK supplier into Guava supplier and vice versa using method reference:

java.util.function.Supplier<String> jdkSupplier = () -> "test"; com.google.common.base.Supplier<String> guavaSupplier = jdkSupplier::get; java.util.function.Supplier<String> jdkSupplierBack = guavaSupplier::get; 

So it's not a big problem to switch between Guava and JDK functions.

like image 153
Tagir Valeev Avatar answered Oct 15 '22 01:10

Tagir Valeev


The simplest solution would be

public static <T> Supplier<T> memoize(Supplier<T> original) {     ConcurrentHashMap<Object, T> store=new ConcurrentHashMap<>();     return ()->store.computeIfAbsent("dummy", key->original.get()); } 

However, the simplest is not always the most efficient.

If you want a clean and efficient solution, resorting to an anonymous inner class to hold the mutable state will pay off:

public static <T> Supplier<T> memoize1(Supplier<T> original) {     return new Supplier<T>() {         Supplier<T> delegate = this::firstTime;         boolean initialized;         public T get() {             return delegate.get();         }         private synchronized T firstTime() {             if(!initialized) {                 T value=original.get();                 delegate=() -> value;                 initialized=true;             }             return delegate.get();         }     }; } 

This uses a delegate supplier which will do the first time operation and afterwards, replace itself with a supplier that unconditionally returns the captured result of the first evaluation. Since it has final fields semantics, it can be unconditionally returned without any additional synchronization.

Inside the synchronized method firstTime(), there is still an initialized flag needed because in case of concurrent access during initialization, multiple threads may wait at the method’s entry before the delegate has been replaced. Hence, these threads need to detect that the initialization has been done already. All subsequent accesses will read the new delegate supplier and get the value quickly.

like image 31
Holger Avatar answered Oct 15 '22 00:10

Holger