Java 8 是否有对供应商的缓存支持?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/35331327/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 00:00:57  来源:igfitidea点击:

Does Java 8 have cached support for suppliers?

javajava-8guava

提问by Cherry

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

番石榴库有它自己的Supplier,它不扩展 Java 8 Supplier。番石榴还为供应商提供了一个缓存 - Suppliers#memoize.

Is there something similar, but for Java 8 Suppliers?

有没有类似的东西,但对于 Java 8 供应商?

回答by Tagir Valeev

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

没有用于记忆的内置 Java 函数,尽管实现它并不难,例如,像这样:

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:

请注意,存在不同的实现方法。如果记忆化的供应商同时从不同的线程多次请求,则上述实现可能会多次调用委托。有时,这种实现优于使用锁的显式同步。如果首选锁定,则可以使用 DCL:

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:

另请注意,正如@LouisWasserman 在评论中正确提到的那样,您可以使用方法引用轻松地将 JDK 供应商转换为 Guava 供应商,反之亦然:

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.

所以在 Guava 和 JDK 功能之间切换不是什么大问题。

回答by Holger

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 finalfields semantics, it can be unconditionally returned without any additional synchronization.

这使用了一个委托供应商,它将执行第一次操作,然后将自己替换为无条件返回第一次评估的捕获结果的供应商。由于它具有final字段语义,因此可以无条件返回而无需任何额外的同步。

Inside the synchronizedmethod firstTime(), there is still an initializedflag 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.

synchronized方法内部firstTime(),仍然initialized需要一个标志,因为在初始化期间并发访问的情况下,多个线程可能会在替换委托之前在方法的入口处等待。因此,这些线程需要检测初始化已经完成。所有后续访问都会读取新的委托供应商并快速获取值。

回答by Kohei Nozaki

A simple wrapper for Guava 20 on Java 8:

Java 8 上 Guava 20 的简单包装器:

static <T> java.util.function.Supplier<T> memoize(java.util.function.Supplier<? extends T> supplier) {
    return com.google.common.base.Suppliers.memoize(supplier::get)::get;
}