Je voudrais implémenter l'initialisation de champ paresseux (ou l'initialisation différée) sans une instruction if et en tirant parti de lambdas. Donc, j'aimerais avoir le même comportement de la propriété Foo
suivante mais sans la if
:
class A<T>{
private T fooField;
public T getFoo(){
if( fooField == null ) fooField = expensiveInit();
return fooField;
}
}
Ignorer le fait que cette solution ne garantit pas une utilisation sûre pour: 1) le multi-threading; 2) null
en tant que valeur valide de T
.
Donc, pour exprimer l'intention de différer l'initialisation de fooField
jusqu'à sa première utilisation, j'aimerais déclarer la fooField
de type Supplier<T>
telle que:
class A<T>{
private Supplier<T> fooField = () -> expensiveInit();
public T getFoo(){
return fooField.get();
}
}
puis dans la propriété getFoo
, je renverrais simplement fooField.get()
. Mais maintenant, je veux que les prochains appels à la propriété getFoo
évitent la expensiveInit()
et renvoient simplement l'instance précédente T
.
Comment puis-je atteindre cet objectif sans utiliser une if
?
Malgré les conventions de dénomination et le remplacement de ->
by =>
, cet exemple peut également être considéré en C #. Cependant, NET Framework version 4 fournit déjà un Lazy<T>
avec la sémantique souhaitée.
Dans votre lambda actuel, vous pouvez simplement mettre à jour la fooField
avec un nouveau lambda, tel que:
class A<T>{
private Supplier<T> fooField = () -> {
T val = expensiveInit();
fooField = () -> val;
return val;
};
public T getFoo(){
return fooField.get();
}
}
Encore une fois, cette solution n’est pas thread-safe comme le .Net Lazy<T>
et ne garantit pas que les appels simultanés à la propriété getFoo
retournent le même résultat.
En prenant la solution de Miguel Gamboa et en essayant de minimiser le code par champ sans sacrifier son élégance, je suis arrivé à la solution suivante:
interface Lazy<T> extends Supplier<T> {
Supplier<T> init();
public default T get() { return init().get(); }
}
static <U> Supplier<U> lazily(Lazy<U> lazy) { return lazy; }
static <T> Supplier<T> value(T value) { return ()->value; }
Supplier<Baz> fieldBaz = lazily(() -> fieldBaz=value(expensiveInitBaz()));
Supplier<Goo> fieldGoo = lazily(() -> fieldGoo=value(expensiveInitGoo()));
Supplier<Eep> fieldEep = lazily(() -> fieldEep=value(expensiveInitEep()));
Le code par champ n’est que légèrement plus grand que dans la solution Stuart Marks mais la propriété Nice de la solution originale est conservée: après la première requête, il n’y aura plus qu’une variable Supplier
légère qui renvoie sans condition la valeur déjà calculée.
L’approche adoptée par La réponse de Miguel Gamboa est excellente:
private Supplier<T> fooField = () -> {
T val = expensiveInit();
fooField = () -> val;
return val;
};
Cela fonctionne bien pour les champs paresseux uniques. Toutefois, si plusieurs champs doivent être initialisés de cette manière, il convient de copier et de modifier le passe-partout. Un autre champ devrait être initialisé comme ceci:
private Supplier<T> barField = () -> {
T val = expensiveInitBar(); // << changed
barField = () -> val; // << changed
return val;
};
Si vous pouvez supporter un appel de méthode supplémentaire par accès après l'initialisation, procédez comme suit. Premièrement, j'écrirais une fonction d'ordre supérieur qui renvoie une instance de fournisseur contenant la valeur mise en cache:
static <Z> Supplier<Z> lazily(Supplier<Z> supplier) {
return new Supplier<Z>() {
Z value; // = null
@Override public Z get() {
if (value == null)
value = supplier.get();
return value;
}
};
}
Une classe anonyme est appelée ici car elle a un état mutable, qui est la valeur mise en cache de la valeur initialisée.
Ensuite, il devient assez facile de créer de nombreux champs initialisés tardivement:
Supplier<Baz> fieldBaz = lazily(() -> expensiveInitBaz());
Supplier<Goo> fieldGoo = lazily(() -> expensiveInitGoo());
Supplier<Eep> fieldEep = lazily(() -> expensiveInitEep());
Note: Je vois dans la question qu'il stipule "sans utiliser une if
". Il n’était pas clair pour moi si la préoccupation ici était d’éviter le temps d’exécution coûteux d’un if conditionnel (vraiment, c’est vraiment pas cher) ou s’il s’agissait plutôt d’éviter de répéter le if conditionnel à chaque passage. J'ai supposé que c'était le dernier cas et ma proposition répond à cette préoccupation. Si vous êtes préoccupé par la surcharge d'exécution d'une conditionnelle if, vous devez également tenir compte de la surcharge liée à l'invocation d'une expression lambda.
Project Lombok fournit une @Getter(lazy = true)
annotation qui répond exactement à vos besoins.
En créant une petite interface et en combinant 2 nouvelles fonctionnalités introduites dans Java 8:
@FunctionalInterface
annotation (permet d'affecter un lambda à une déclaration)default
mot-clé (définir une implémentation, comme une classe abstraite - mais dans une interface)Il est possible d’obtenir le même comportement Lazy<T>
que celui observé en C #.
Usage
Lazy<String> name = () -> "Java 8";
System.out.println(name.get());
Lazy.Java (copiez et collez cette interface dans un endroit accessible)
import Java.util.function.Supplier;
@FunctionalInterface
public interface Lazy<T> extends Supplier<T> {
abstract class Cache {
private volatile static Map<Integer, Object> instances = new HashMap<>();
private static synchronized Object getInstance(int instanceId, Supplier<Object> create) {
Object instance = instances.get(instanceId);
if (instance == null) {
synchronized (Cache.class) {
instance = instances.get(instanceId);
if (instance == null) {
instance = create.get();
instances.put(instanceId, instance);
}
}
}
return instance;
}
}
@Override
default T get() {
return (T) Cache.getInstance(this.hashCode(), () -> init());
}
T init();
}
L'extrait suivant illustre le cycle de vie de cette classe d'assistance
static Lazy<String> name1 = () -> {
System.out.println("lazy init 1");
return "name 1";
};
static Lazy<String> name2 = () -> {
System.out.println("lazy init 2");
return "name 2";
};
public static void main (String[] args) throws Java.lang.Exception
{
System.out.println("start");
System.out.println(name1.get());
System.out.println(name1.get());
System.out.println(name2.get());
System.out.println(name2.get());
System.out.println("end");
}
va sortir
start
lazy init 1
name 1
name 1
lazy init 2
name 2
name 2
end
Que dis-tu de ça? alors vous pouvez faire quelque chose comme ceci en utilisant LazyInitializer
d'Apache Commons: https://commons.Apache.org/proper/commons-lang/javadocs/api-3.1/org/Apache/commons/lang3/concurrent/LazyInitializer. html
private static Lazy<Double> _lazyDouble = new Lazy<>(()->1.0);
class Lazy<T> extends LazyInitializer<T> {
private Supplier<T> builder;
public Lazy(Supplier<T> builder) {
if (builder == null) throw new IllegalArgumentException();
this.builder = builder;
}
@Override
protected T initialize() throws ConcurrentException {
return builder.get();
}
}
Voici un moyen qui fonctionne également si vous souhaitez passer des arguments (que vous n'avez pas lors de l'initialisation de l'interface fonctionnelle) à votre méthode expensiveInit
.
public final class Cache<T> {
private Function<Supplier<? extends T>, T> supplier;
private Cache(){
supplier = s -> {
T value = s.get();
supplier = n -> value;
return value;
};
}
public static <T> Supplier<T> of(Supplier<? extends T> creater){
Cache<T> c = new Cache<>();
return () -> c.supplier.apply(creater);
}
public static <T, U> Function<U, T> of(Function<? super U, ? extends T> creater){
Cache<T> c = new Cache<>();
return u -> c.supplier.apply(() -> creater.apply(u));
}
public static <T, U, V> BiFunction<U, V, T> of(BiFunction<? super U, ? super V, ? extends T> creater){
Cache<T> c = new Cache<>();
return (u, v) -> c.supplier.apply(() -> creater.apply(u, v));
}
}
L'utilisation est la même que Stuart Marks ' answer:
private final Function<Foo, Bar> lazyBar = Cache.of(this::expensiveBarForFoo);
Vous pouvez faire quelque chose dans ce sens:
private Supplier heavy = () -> createAndCacheHeavy();
public Heavy getHeavy()
{
return heavy.get();
}
private synchronized Heavy createAndCacheHeavy()
{
class HeavyFactory implements Supplier
{
private final Heavy heavyInstance = new Heavy();
public Heavy get()
{
return heavyInstance;
}
}
if(!HeavyFactory.class.isInstance(heavy))
{
heavy = new HeavyFactory();
}
return heavy.get();
}
J'ai récemment vu cela comme une idée de Venkat Subramaniam. J'ai copié le code de cette page .
L'idée de base est que le fournisseur, une fois appelé, se remplace par une implémentation d'usine plus simple qui renvoie l'instance initialisée.
C'était dans le contexte de l'initialisation paresseuse thread-safe d'un singleton, mais vous pouvez aussi l'appliquer à un champ normal, évidemment.
Eh bien, je ne suggère pas vraiment de ne pas avoir de "si", mais voici mon point de vue sur le sujet:
Une méthode simple consiste à utiliser AtomicReference (l'opérateur ternaire ressemble toujours à un "if"):
private final AtomicReference<Something> lazyVal = new AtomicReference<>();
void foo(){
final Something value = lazyVal.updateAndGet(x -> x != null ? x : expensiveCreate());
//...
}
Mais il y a aussi toute la magie de la sécurité du fil dont on n'a peut-être pas besoin. Donc, je le ferais comme Miguel avec une petite tournure:
Etant donné que j'aime les one-liners simples, j'utilise simplement un opérateur ternaire (là encore, il se lit comme un "if"), mais je laissais l'ordre d'évaluation de Java faire sa magie pour définir le champ:
public static <T> Supplier<T> lazily(final Supplier<T> supplier) {
return new Supplier<T>() {
private T value;
@Override
public T get() {
return value != null ? value : (value = supplier.get());
}
};
}
l'exemple de modification de champ de gerardw ci-dessus, qui fonctionne sans "si", peut également être simplifié. Nous n'avons pas besoin de l'interface. Nous avons juste besoin d'exploiter à nouveau "l'astuce" ci-dessus: le résultat d'un opérateur d'affectation est la valeur assignée, nous pouvons utiliser des crochets pour forcer l'ordre d'évaluation. Donc, avec la méthode ci-dessus, c'est juste:
Tout ce dont nous avons besoin est ceci:
static <T> Supplier<T> value(final T value) {
return () -> value;
}
Supplier<Point> p2 = () -> (p2 = value(new Point())).get();
Si vous avez besoin de quelque chose qui se rapproche du comportement de Lazy
en C #, qui vous donne la sécurité des threads et vous garantisse que vous obtenez toujours la même valeur, il n'existe pas de moyen simple d'éviter if
.
Vous devrez utiliser un champ volatile et une double vérification du verrouillage. Voici la version la plus basse d'empreinte mémoire d'une classe qui vous donne le comportement C #:
public abstract class Lazy<T> implements Supplier<T> {
private enum Empty {Uninitialized}
private volatile Object value = Empty.Uninitialized;
protected abstract T init();
@Override
public T get() {
if (value == Empty.Uninitialized) {
synchronized (this) {
if (value == Empty.Uninitialized) {
value = init();
}
}
}
return (T) value;
}
}
Ce n'est pas si élégant à utiliser. Vous devez créer des valeurs paresseuses comme ceci:
final Supplier<Baz> someBaz = new Lazy<Baz>() {
protected Baz init(){
return expensiveInit();
}
}
Vous pouvez gagner en élégance au prix d'une mémoire supplémentaire, en ajoutant une méthode d'usine comme celle-ci:
public static <V> Lazy<V> lazy(Supplier<V> supplier) {
return new Lazy<V>() {
@Override
protected V init() {
return supplier.get();
}
};
}
Maintenant, vous pouvez créer des valeurs paresseuses sécurisées pour les threads simplement comme ceci:
final Supplier<Foo> lazyFoo = lazy(() -> fooInit());
final Supplier<Bar> lazyBar = lazy(() -> barInit());
final Supplier<Baz> lazyBaz = lazy(() -> bazInit());
La solution de Stuart Mark, avec une classe explicite. (Je pense que ce soit "meilleur" est une chose personnelle
public class ScriptTrial {
static class LazyGet<T> implements Supplier<T> {
private T value;
private Supplier<T> supplier;
public LazyGet(Supplier<T> supplier) {
value = null;
this.supplier = supplier;
}
@Override
public T get() {
if (value == null)
value = supplier.get();
return value;
}
}
Supplier<Integer> lucky = new LazyGet<>(()->seven());
int seven( ) {
return 7;
}
@Test
public void printSeven( ) {
System.out.println(lucky.get());
System.out.println(lucky.get());
}
}
Que diriez-vous de cette . Certains switcheroos fonctionnels J8 pour éviter les ifs sur chaque accès .
import Java.util.function.Supplier;
public class Lazy<T> {
private T obj;
private Supplier<T> creator;
private Supplier<T> fieldAccessor = () -> obj;
private Supplier<T> initialGetter = () -> {
obj = creator.get();
creator = null;
initialGetter = null;
getter = fieldAccessor;
return obj;
};
private Supplier<T> getter = initialGetter;
public Lazy(Supplier<T> creator) {
this.creator = creator;
}
public T get() {
return getter.get();
}
}