studio - ¿Java 8 tiene soporte en caché para proveedores?
manual android studio avanzado (2)
La biblioteca de guava tiene su propio Supplier
que no extiende Java 8 Supplier
. También guava proporciona un caché para proveedores - Suppliers#memoize
.
¿Hay algo similar, pero para los proveedores de Java 8?
La solución más simple sería
public static <T> Supplier<T> memoize(Supplier<T> original) {
ConcurrentHashMap<Object, T> store=new ConcurrentHashMap<>();
return ()->store.computeIfAbsent("dummy", key->original.get());
}
Sin embargo, el más simple no es siempre el más eficiente.
Si desea una solución limpia y eficiente, recurrir a una clase interna anónima para mantener el estado variable dará sus frutos:
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();
}
};
}
Esto utiliza un proveedor delegado que realizará la primera operación y luego se reemplazará con un proveedor que devuelve incondicionalmente el resultado capturado de la primera evaluación. Como tiene final
semántica de campos final
, se puede devolver incondicionalmente sin ninguna sincronización adicional.
Dentro del método synchronized
firstTime()
, todavía hay una bandera initialized
necesaria porque en caso de acceso concurrente durante la inicialización, múltiples hilos pueden esperar en la entrada del método antes de que el delegado haya sido reemplazado. Por lo tanto, estos subprocesos necesitan detectar que la inicialización ya se ha realizado. Todos los accesos posteriores leerán al nuevo proveedor delegado y obtendrán el valor rápidamente.
No hay una función incorporada de Java para la memorización, aunque no es muy difícil implementarla, por ejemplo, así:
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;
};
}
Tenga en cuenta que existen diferentes enfoques de implementación. La implementación anterior puede llamar al delegado varias veces si el proveedor registrado solicitó simultáneamente varias veces desde los diferentes hilos. En ocasiones, dicha implementación es preferible a la sincronización explícita con bloqueo. Si se prefiere bloqueo, entonces DCL podría usarse:
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;
};
}
También tenga en cuenta que, como @LouisWasserman mencionó correctamente en los comentarios, puede transformar fácilmente el proveedor de JDK en proveedor de Guava y viceversa utilizando la referencia de método:
java.util.function.Supplier<String> jdkSupplier = () -> "test";
com.google.common.base.Supplier<String> guavaSupplier = jdkSupplier::get;
java.util.function.Supplier<String> jdkSupplierBack = guavaSupplier::get;
Por lo tanto, no es un gran problema cambiar entre las funciones de Guava y JDK.