В библиотеке guava есть собственный Supplier
, который не расширяет Java 8 Supplier
. Кроме того, guava предоставляет кэш для поставщиков - Suppliers#memoize
.
Есть ли что-то подобное, но для Java 8 поставщиков?
В библиотеке guava есть собственный Supplier
, который не расширяет Java 8 Supplier
. Кроме того, guava предоставляет кэш для поставщиков - Suppliers#memoize
.
Есть ли что-то подобное, но для Java 8 поставщиков?
Простейшим решением было бы
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
, потому что в случае одновременного доступа во время инициализации несколько потоков могут ждать в элементе методов до того, как делегат будет заменен. Следовательно, эти потоки должны обнаружить, что инициализация уже выполнена. Все последующие обращения будут читать новый поставщик делегатов и быстро получать значение.
Нет встроенной функции 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;
};
}
Обратите внимание, что существуют разные подходы к реализации. Вышеупомянутая реализация может вызвать делегата несколько раз, если замешанный поставщик запрашивал одновременно несколько раз из разных потоков. Иногда такая реализация предпочтительнее явной синхронизации с блокировкой. Если блокировка предпочтительнее, тогда можно использовать 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 в поставщика 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;
Так что это не большая проблема для переключения между функциями Guava и JDK.