web-dev-qa-db-fra.com

Est-il permis / conseillé de réutiliser un collecteur?

J'ai beaucoup de taches dans mon code qui font:

someStream.collect(Collectors.toList())

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?

45
glglgl

Je pense que c'est plus une question de style, mais laisse quelques réflexions:

  • Il semble être courant de s'entraîner à pas utiliser un tel objet collecteur CONST. En ce sens: cela pourrait surprendre certains lecteurs, et surprendre des lecteurs est rarement une bonne chose à faire.
  • Ensuite: peu de code peut être simplement "copié" (et ne devrait probablement pas éviter la duplication de code); mais quand même: pointer vers un objet collecteur distinct peut rendre un peu plus difficile le re-factorisation ou la réutilisation de vos constructions de flux.
  • Au-delà de cela: vous l'avez dit vous-même; réutilisation du collecteur dépend de l'implémentation a sans état. Donc, vous vous rendez dépendant de toute implémentation sans état. Probablement pas un problème; mais peut-être un risque à garder à l'esprit!
  • Probablement plus important: en surface, votre idée ressemble à un joli moyen pour optimisation. Enfin bon; lorsque vous vous inquiétez des "effets de performance" de l'utilisation de flux, alors la création d'un seul objet du collecteur final "ne le coupera pas"!

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".

35
GhostCat

É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.

30
Holger

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 :)

13
Honza Zidek

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.

6
Eugene

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!

3
OldCurmudgeon

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.

1