Paresseux champ d'initialisation avec des lambdas
Je tiens à mettre en œuvre paresseux champ d'initialisation (ou différé d'initialisation) sans une instruction if et de profiter des lambdas. Donc, je voudrais avoir le même comportement de la suite Foo
bien, mais sans le if
:
class A<T>{
private T fooField;
public T getFoo(){
if( fooField == null ) fooField = expensiveInit();
return fooField;
}
}
Ignorer le fait que cette solution n'est pas de garantir l'utilisation sûre pour: 1) le multi-threading; 2) null
comme une valeur valide de T
.
Donc, pour exprimer l'intention que l'initialisation de la fooField
est différé jusqu'à sa première utilisation, je voudrais déclarer la fooField
du type Supplier<T>
tels que:
class A<T>{
private Supplier<T> fooField = () -> expensiveInit();
public T getFoo(){
return fooField.get();
}
}
et puis dans le getFoo
bien, je voudrais juste revenir fooField.get()
. Mais maintenant, je veux que la prochaine invocations à getFoo
propriété éviter la expensiveInit()
et il suffit de retourner le précédent T
instance.
Comment puis-je réaliser que sans l'aide d'un if
?
Malgré les conventions de nommage et le remplacement de la ->
par =>
, alors cet exemple pourrait être également considéré comme en C#. Toutefois, le montant NET Framework version 4 dispose déjà d'un Lazy<T>
avec le désiré de la sémantique.
- Il suffit de faire comme ce post stackoverflow.com/questions/6847721/when-should-i-use-lazyt
- C'est pour .Net. Mais est-il une
Lazy
équivalent en Java? - Je pense que la confusion est qu'il y a à la fois le C# et Java-8 balises sur votre question. Vous pourriez peut-être modifier la question pour le rendre plus clair, vous voulez un Java-8 solution qui imite C# comportement.
- Voir la Goyave est les Fournisseurs.memoize
- Connexes: stackoverflow.com/questions/8297705/...
- Vous pouvez aussi consulter vavr la solution.
Vous devez vous connecter pour publier un commentaire.
Au sein de votre effectif lambda, vous pouvez simplement mettre à jour le
fooField
avec une nouvelle lambda, tels que:À nouveau cette solution n'est pas thread-safe, comme c'est le .Net
Lazy<T>
, et de ne pas s'assurer que les appels simultanés à lagetFoo
propriété retournent le même résultat.Prendre Miguel Gamboa la solution et en essayant de minimiser le champ par champ code sans sacrifier son élégance, je suis venu à la solution suivante:
Le champ par champ code seulement légèrement plus grand que dans Stuart Marques de solution, mais il conserve la propriété de l'original de la solution après la première requête, il n'y aura qu'un léger
Supplier
qui sans condition renvoie la déjà de la valeur calculée.expensiveInit…()
s'avère être en fait une méthode triviale).static final
Objet. Environ la moitié du coût est liée à des génériques et l'effacement semble - t-ilT
est effacée àObject
qui doit être coulé retour àInteger
où l'indice de référence des méthodes de retours (le casting est également nécessaire dans le monde réel). Si vous déclarez un type spécifique deLazy
classe de réduire ce coût. Mais dans l'ensemble, je voudrais juste faire tout ce qui est plus simple, probablement le long des lignes de Stuart suggestion.L'approche adoptée par Miguel Gamboa réponse est un parfait:
Il fonctionne bien pour une paresseuse champs. Cependant, si plus d'un champ doit être initialisé de cette façon, le standard devra être copié et modifié. Un autre domaine devront être initialisé comme ceci:
Si vous avez un appel de méthode par l'accès après l'initialisation, je le ferais comme suit. Tout d'abord, je voudrais écrire une fonction d'ordre supérieur qui renvoie une instance de Fournisseur qui contient la valeur mise en cache:
Une classe anonyme est ici parce qu'il a mutable état, qui est la mise en cache de la initialisée valeur.
Ensuite, il devient assez facile de créer de nombreux initialisées champs:
Remarque: je vois dans la question qu'il prévoit que "sans l'aide d'un
if
". Il n'était pas clair pour moi si le souci ici est sur le souci d'éviter le runtime cher d'un si-conditionnel (vraiment, c'est assez bon marché) ou si c'est plus sur la façon d'éviter d'avoir à répéter la si-à condition que dans tous les getter. J'ai supposé que c'était le dernier, et ma proposition répond à cette préoccupation. Si vous êtes inquiet au sujet de gestion d'exécution d'un si (conditionnel, alors vous devriez également prendre la surcharge de l'invocation d'une expression lambda en compte.lazily
fonction d'utilitéProjet De Lombok fournit un
@Getter(lazy = true)
annotation qui fait exactement ce dont vous avez besoin.Il est pris en charge,
Par la création d'une petite interface et la combinaison de 2 nouvelles fonctionnalités introduites dans java 8:
@FunctionalInterface
annotation (permet d'assigner un lambda sur la déclaration)default
mot-clé (définir une mise en œuvre, tout comme la classe abstraite -, mais dans une interface)Il est possible d'obtenir la même
Lazy<T>
comportement comme vous l'avez vu en C#.Utilisation
Lazy.java (copiez et collez cette interface en quelque part accessible)
En Ligne Exemple - https://ideone.com/3b9alx
L'extrait de code suivant illustre le cycle de vie de cette classe d'assistance
sortie
Voir la démo en ligne - https://ideone.com/3b9alx
hashCode()
comme une Carte-clé de cette manière. Deux objets peuvent obtenir le même hashCode.java.lang.System.identityHashCode(obj);
faire l'affaire?IdentityHashMap
.Comment à ce sujet? ensuite, vous pouvez faire quelque chose comme ceci en utilisant
LazyInitializer
de Apache Commons: https://commons.apache.org/proper/commons-lang/javadocs/api-3.1/org/apache/commons/lang3/concurrent/LazyInitializer.htmlLazyInitializer
de classe.Vous pourriez faire quelque chose le long de ces lignes :
J'ai récemment vu cela comme une idée par Venkat Subramaniam. J'ai copié le code de cette page.
L'idée de base est que le Fournisseur une fois appelé, remplace lui-même avec une simple usine de mise en œuvre qui renvoie l'instance initialisée.
C'était dans le contexte de thread-safe initialisation d'un singleton, mais vous pouvez également l'appliquer à un champ normal, évidemment.
Voici une manière qui fonctionne également si vous souhaitez passer des arguments (que vous n'avez pas lors de l'initialisation de l'interface fonctionnelle) à votre
expensiveInit
méthode.Utilisation est la même que Stuart Marques" réponse:
Si vous besoin de quelque chose qui se rapproche le comportement de
Lazy
en C#, qui vous donne la sécurité des threads, et une garantie que vous obtenez toujours la même valeur, il n'y a pas de façon simple d'éviterif
.Vous aurez besoin d'utiliser un champ volatile et une double vérification de verrouillage. Ici est la plus faible empreinte mémoire version d'une classe qui vous donne le C# comportement:
Il n'est pas élégant à utiliser. Vous devez créer paresseux valeurs comme ceci:
Vous pouvez acquérir une certaine élégance à le coût supplémentaire de la mémoire, par l'ajout d'une usine de méthode comme ceci:
Maintenant, vous pouvez créer de thread safe paresseux valeurs tout simplement comme ceci:
Bien, je n'ai pas vraiment suggèrent ayant pas de "si", mais voici mon point de vue sur la question:
Une méthode simple consiste à utiliser un AtomicReference (l'opérateur ternaire est tout de même un "si"):
Mais il y a ensuite toute sécurité des threads de la magie qu'on n'aurait pas besoin. Donc, je ferais comme Miguel avec un petit twist:
Depuis que j'ai comme un simple one-liners, j'ai simplement utiliser un opérateur ternaire (encore une fois, se lit comme un "si"), mais je préfère le laisser Java de l'évaluation afin de faire sa magie pour définir le champ:
gerardw du champ-de modification de l'exemple ci-dessus, qui fonctionne sans "si", peut être encore simplifiée trop. Nous n'avons pas besoin de l'interface. Nous avons juste besoin de les exploiter au-dessus de "truc" nouveau: Un opérateur d'affectation du résultat est la valeur assignée, on peut utiliser les parenthèses pour forcer l'ordre d'évaluation. Donc, avec la méthode ci-dessus, c'est juste:
Tous nous avons besoin est: est-ce
Comment à ce sujet.
Certains J8 fonctionnelle switcheroos pour éviter fi sur chaque accès.
Avertissement: pas de thread courant.
Stuart Marque de la solution explicite de la classe. (Si c'est "mieux" est une question de préférence personnelle, je pense.)
}
2 solutions, l'une fonctionnelle d'alors et d'un objet (c'est même code), thread-safe, sans "si", et en prenant soin de gestion d'Exception avec le bon type de propagation (pas de solution ici, prendre soin à ce sujet).
Elle est assez courte. Mieux paresseux champs de soutien, géré par le runtime, finira par rendre ce code obsolète...
utilisation :
J'ai d'abord définir un lambda exception :
Puis un Paresseux type :
Version fonctionnelle :
Il renvoie directement un lambda qui finit par avoir le moins de mémoire, si ce n'est pas sur un champ final comme dans l'exemple ci-dessus.
On ne peut pas appliquer de donner une valeur correcte de rappel, au moins, il retourne toujours le même résultat, mais ne peut éviter le "si" (comme dans final sur le terrain).
Version d'objet :
Est entièrement protégé de l'extérieur.
profiter de
Voici une solution à l'aide de Java Proxy (réflexion) et Java 8 Fournisseur.
* En raison de la Procuration d'utilisation, l'initié objet doit implémenter l'interface.
* La différence par rapport à d'autres solutions est l'encapsulation de l'initiation de l'utilisation. Vous commencer à travailler directement avec
DataSource
comme si elle a été initialisé. Il sera initialisé sur la première application de la méthode d'invocation.Utilisation:
Derrière les coulisses: