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

番石榴图书馆拥有自己的Supplier ,不会扩展Java 8 Supplier 。 番石榴也为供应商提供了一个缓存 – Suppliers#memoize

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

最简单的解决方案是

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

但是,最简单的并不总是最有效的。

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

 public static  Supplier memoize1(Supplier original) { return new Supplier() { Supplier 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标志,因为在初始化期间并发访问的情况下,多个线程可能在替换委托之前等待方法的条目。 因此,这些线程需要检测已经完成初始化。 所有后续访问都将读取新的委托供应商并快速获取值。

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

 public static  Supplier memoize(Supplier delegate) { AtomicReference 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  Supplier memoizeLock(Supplier delegate) { AtomicReference 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供应商转换为Guava供应商,反之亦然:

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

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