J'ai beaucoup de taches dans mon code qui font:
someStream.collect(Collectors.toList())
où Collectors.toList()
crée un nouveau collecteur à chaque utilisation.
Cela m'amène à la question de savoir s'il est permis et conseillé de faire quelque chose comme:
private final static Collector<…> TO_LIST = Collectors.toList()
pour chaque type que j'utilise, puis utilise ce seul collecteur comme:
someStream.collect(TO_LIST)
lorsqu'un collecteur est requis.
Comme les collecteurs sont apatrides et juste une collection de fonctions et de caractéristiques, je pense que cela devrait fonctionner, mais OTOH, Collectors.toList()
crée un nouveau CollectorImpl<>
À chaque appel.
Quels sont les inconvénients de la réutilisation d'un collecteur?
Je pense que c'est plus une question de style, mais laisse quelques réflexions:
Ce que je veux dire par là: si vous avez peur de "gaspiller" les performances; vous préférez examiner chaque ligne de code qui utilise des flux pour déterminer si ce flux fonctionne avec "suffisamment" d'objets pour justifier l'utilisation des flux en premier lieu. Ces flux sont livrés avec pas mal de surcharge!
Pour faire court: la communauté Java Java n'a pas encore trouvé de "meilleures pratiques standard" pour les flux; donc mes deux centimes (personnels) pour le moment: préférez les modèles que "tout le monde" utilise - évitez faire votre propre truc. Surtout quand c'est "lié à la performance".
Étant donné que Collector
est fondamentalement un conteneur pour les quatre drapeaux de fonctions et de caractéristiques, il n'y a aucun problème à le réutiliser, mais aussi rarement aucun avantage, car l'impact d'un tel objet léger sur la gestion de la mémoire est négligeable, sinon supprimé entièrement par l'optimiseur de toute façon.
La principale raison de ne pas réutiliser Collector
, comme vu avec le Collectors
intégré, est que vous ne pouvez pas le faire de manière sûre. Lorsque vous proposez un collecteur pour des List
de type arbitraire, vous aurez besoin d'opérations non contrôlées pour toujours distribuer la même instance Collector
. Si vous stockez un Collector
dans une variable correctement typée à la place, pour l'utiliser sans opérations non contrôlées, vous ne pouvez l'utiliser que pour un type de List
, pour rester avec cet exemple.
Dans le cas de Collections.emptyList()
, etc., les développeurs JRE ont suivi une voie différente, mais les constantes EMPTY_LIST
, EMPTY_MAP
, EMPTY_SET
existait déjà avant l'introduction des génériques, et je dirais qu'ils sont plus polyvalents que les quelques Collectors
pouvant être mis en cache, qui ne sont que quatre cas spéciaux sur les autres plus de trente collecteurs intégrés, qui ne peuvent pas être mis en cache en raison de leurs paramètres de fonction. Étant donné que les paramètres de fonction sont souvent implémentés via des expressions lambda, qui génèrent des objets d'identité/d'égalité non spécifiés, un cache les mappant avec des instances de collecteur aurait une efficacité imprévisible, mais très probablement beaucoup moins efficace que le gestionnaire de mémoire ne traitera les instances temporaires.
C'est une bonne pratique pour une bibliothèque de fournir un méthode d'usine pour obtenir des objets utiles. Comme la bibliothèque a fourni une telle méthode: Collectors.toList()
, c'est encore une bonne pratique de laisser la bibliothèque décider de créer ou non une nouvelle instance à chaque fois que l'objet est demandé, au lieu d'altérer la bibliothèque, diminuant ainsi la lisibilité et risquant les problèmes futurs lorsque l'implémentation change.
Cela doit être ajouté à la réponse de GhostCat et Holger comme argument de soutien :)
Juste une petite note, ce que @Holger dit dans sa réponse à propos de l'optimiseur intelligent et du remplacement complet de cette construction est totalement faisable et s'appelle un scalar replacement
. Lorsqu'un objet utilisé dans une méthode est déconstruit et ses champs sont stack allocated like normal local variables
. De sorte que le Collector
résultant pourrait ne pas être traité au niveau de la JVM comme un objet en soi. Cela se produirait à JIT time
.
Le problème classique de l'utilisation d'un seul objet statique pour remplacer celui créé à la volée est la mutabilité. Une analyse rapide de la source Java 8 met en évidence le Set<Characteristics>
champ comme problème possible.
De toute évidence, il serait possible pour un code quelque part de faire quelque chose comme:
private final static Collector<Object, ?, List<Object>> TO_LIST = Collectors.toList();
public void test() {
// Any method could do this (no idea why but it should be possible).
TO_LIST.characteristics().add(Collector.Characteristics.IDENTITY_FINISH);
}
Cela pourrait changer globalement la fonctionnalité de chaque utilisation de TO_LIST
ce qui pourrait créer des bugs très obscurs.
Alors à mon humble avis - ne le faites pas!
Ce serait un cas d'optimisation prématurée. La création d'objets est assez bon marché. Sur un ordinateur portable normal, je m'attendrais à pouvoir créer entre 10 et 50 millions d'objets par seconde. Avec ces chiffres à l'esprit, tout l'exercice devient inutile.