La bibliothèque de goyave a sa propre Supplier
qui ne s'étend pas Java 8 Supplier
. Aussi goyave fournit un cache pour les fournisseurs - Suppliers#memoize
.
Y a-t-il quelque chose de similaire, mais pour Java 8 Fournisseurs?
Il n'y a pas de fonction intégrée Java pour la mémorisation, bien qu'il ne soit pas très difficile de l'implémenter, par exemple, comme ceci:
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;
};
}
Notez que différentes approches de mise en œuvre existent. L'implémentation ci-dessus peut appeler le délégué plusieurs fois si le fournisseur mémorisé a demandé simultanément plusieurs fois aux différents threads. Parfois, une telle implémentation est préférée à la synchronisation explicite avec verrouillage. Si le verrouillage est préféré, alors DCL pourrait être utilisé:
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;
};
}
Notez également que, comme @LouisWasserman l'a correctement mentionné dans les commentaires, vous pouvez facilement transformer un fournisseur JDK en fournisseur Guava et vice versa en utilisant la référence de méthode:
Java.util.function.Supplier<String> jdkSupplier = () -> "test";
com.google.common.base.Supplier<String> guavaSupplier = jdkSupplier::get;
Java.util.function.Supplier<String> jdkSupplierBack = guavaSupplier::get;
Ce n'est donc pas un gros problème pour basculer entre les fonctions Guava et JDK.
La solution la plus simple serait
public static <T> Supplier<T> memoize(Supplier<T> original) {
ConcurrentHashMap<Object, T> store=new ConcurrentHashMap<>();
return ()->store.computeIfAbsent("dummy", key->original.get());
}
Cependant, le plus simple n'est pas toujours le plus efficace.
Si vous voulez une solution propre et efficace, le recours à une classe interne anonyme pour maintenir l'état mutable sera payant:
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();
}
};
}
Celui-ci utilise un fournisseur délégué qui effectuera la première opération et se remplacera ensuite par un fournisseur qui renvoie sans condition le résultat capturé de la première évaluation. Comme il possède la sémantique des champs final
, il peut être renvoyé sans condition sans synchronisation supplémentaire.
Dans la méthode synchronized
firstTime()
, il y a toujours un indicateur initialized
nécessaire car en cas d'accès simultané pendant l'initialisation, plusieurs threads peuvent attendre l'entrée de la méthode avant que le délégué n'ait été remplacé. Par conséquent, ces threads doivent détecter que l'initialisation a déjà été effectuée. Tous les accès ultérieurs liront le nouveau fournisseur délégué et obtiendront la valeur rapidement.