7

This method, as I know, memorizes (caches) the value of supplier that passed as the parameter. As I understand It behaves like singleton pattern. Can anyone explain how it works?

  public static <T> Supplier<T> memoize(final Supplier<? extends T> valueSupplier)
  {
    final List<T> memoryList= new ArrayList<>();
    return () -> {
      if (memoryList.isEmpty()) {
        memoryList.add(valueSupplier.get());
      }
      return memoryList.get(0);
    };
  }

Usage like this:

Supplier<SomeClass> cachedValue = memoize(() -> someClassObject.getSomeValueToBeCached());
cachedValue.get().doMethod();
Eugene
  • 117,005
  • 15
  • 201
  • 306
memor1s
  • 152
  • 2
  • 10
  • Which part is causing you problems? Is it the lambda? Maybe try rewriting it without a lambda, but using an anonymous inner class. – Rolf Schäuble May 25 '17 at 09:31
  • @RolfSchäuble There are no problems, I do not completely understand how it works. – memor1s May 25 '17 at 09:32
  • 4
    As it is written now, it doesn't correctly memorize the value in multithreaded access case, because there are three distinct operations: checking if list is empty, loading the value and adding value to list, and each of those can be race target. – M. Prokhorov May 25 '17 at 09:34
  • 1
    @M.Prokhorov Besides, it's a waste to use a list for this... An `AtomicReference` would do the job in a multi-threaded environment and a simple `Holder` class would suffice for single-threaded scenarios. – fps May 25 '17 at 17:46
  • See [Does Java 8 have cached support for suppliers?](https://stackoverflow.com/q/35331327/2711488) and [Lazy field initialization with lambdas](https://stackoverflow.com/q/29132884/2711488)… – Holger May 31 '17 at 11:22

2 Answers2

7

Ok, so let's rewrite the code in small steps towards more old-style, verbose Java. Maybe that makes it simpler to understand.

First step: get rid of the lambda:

public static <T> Supplier<T> memoize(final Supplier<? extends T> valueSupplier)
{
    final List<T> memoryList= new ArrayList<>();
    return new Supplier<T>() {
        @Override
        public T get() {
            if (memoryList.isEmpty()) {
                memoryList.add(valueSupplier.get());
            }
            return memoryList.get(0);
        }
    };
}

Next step: extract the anonymous inner class into a standalone class. While the anonymous class had access to the local variables of its containing method (memoryList), a "normal" class has not, so we're moving the list into the caching supplier.

class CachingSupplier<T> implements Supplier<T> {

    final List<T> memoryList= new ArrayList<>();
    private Supplier<T> originalSupplier;

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

    @Override
    public T get() {
        if (memoryList.isEmpty()) {
            memoryList.add(originalSupplier.get());
        }
        return memoryList.get(0);
    }
}

public static <T> Supplier<T> memoize(final Supplier<? extends T> valueSupplier) {
  return new CachingSupplier<>(valueSupplier);
}

Finally, let's replace the ArrayList by a simple reference.

class CachingSupplier<T> implements Supplier<T> {

    private T cachedValue;
    private Supplier<T> originalSupplier;

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

    @Override
    public T get() {
        if (cachedValue == null) {
            cachedValue = originalSupplier.get();
        }
        return cachedValue;
    }
}

public static <T> Supplier<T> memoize(final Supplier<? extends T> valueSupplier) {
  return new CachingSupplier<>(valueSupplier);
}

Maybe that's easier to understand. If you are still unclear about something, just ask in a comment and I'll try to explain it.

Rolf Schäuble
  • 690
  • 4
  • 15
  • So anonymous inner local class places all it's outer dependencies inside itself like references? – memor1s May 25 '17 at 09:46
  • 1
    Yes. The anonymous class has access to the variables of its defining scope (in this example: `memoryList`). – Rolf Schäuble May 25 '17 at 09:47
  • @VitalyBelkevich, check out [this very good video](https://www.youtube.com/watch?v=MLksirK9nnE) about how lambdas work and why, it helped me a lot when I was learning it. – M. Prokhorov May 29 '17 at 13:10
4

How about this?

public static <T> Supplier<T> memoize(final Supplier<? extends T> factory) {
    final List<T> cache = new ArrayList<>();
    return () -> {
               // v--- check the value is cached?
        if (cache.isEmpty()) {
                           // v--- return the value created by factory
            cache.add(factory.get());
               // ^--- adding the value into the cache
        }
        return cache.get(0); 
                  // ^--- return the cached value
    };
}

Usage

Supplier<String> factory = ()-> new String("foo"); 

assert factory.get() == factory.get(); // return false;
assert memoize(factory).get() == memoize(factory).get(); //return false;

                 // v--- storing the memoized factory for using as further
Supplier<String> memoized = memoize(original);
assert memoized.get() == memoized.get(); // return true.
                    // ^--- they are the same.  
holi-java
  • 29,655
  • 7
  • 72
  • 83