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

时间:2016-02-11 05:18:54

标签: java java-8 guava

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

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

3 个答案:

答案 0 :(得分:21)

没有内置的Java函数用于memoization,虽然它实现起来并不是很难,例如,像这样:

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;
    };
}

请注意,存在不同的实现方法。如果被记忆的供应商从不同的线程同时多次请求,则上述实现可以多次调用该代表。有时这种实现比使用lock的显式同步更受欢迎。如果首选锁定,则可以使用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;
    };
}

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

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

因此,在Guava和JDK函数之间切换并不是一个大问题。

答案 1 :(得分:19)

最简单的解决方案是

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

然而,最简单的并不总是效率最高。

如果你想要一个干净有效的解决方案,诉诸匿名内部类来保持可变状态将会有所回报:

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();
        }
    };
}

这使用代理供应商,该代理供应商将在第一次操作时执行,之后将其自身替换为无条件返回第一次评估的捕获结果的供应商。由于它具有final字段语义,因此可以无条件地返回它而无需任何其他同步。

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

答案 2 :(得分:4)

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;
}