Comment ajouter des éléments de Java8 flux dans une Liste existante
Javadoc de Collecteur montre comment recueillir des éléments d'un flux dans une nouvelle Liste. Est-il un one-liner qui ajoute les résultats dans un ArrayList?
- Il y a déjà une réponse, here. Recherchez l'élément “Ajouter à un
Collection
”
Vous devez vous connecter pour publier un commentaire.
REMARQUE: nosid réponse montre comment ajouter à une collection existante à l'aide de
forEachOrdered()
. C'est une technique efficace pour la mutation des collections existantes. Ma réponse montre pourquoi vous ne devriez pas utiliser unCollector
à muter une collection existante.La réponse courte est pas, au moins, pas en général, vous ne devriez pas utiliser un
Collector
de modifier une collection existante.La raison en est que les collectionneurs sont conçus pour soutenir le parallélisme, même sur les collections qui ne sont pas thread-safe. La façon de faire cela est d'avoir chaque thread fonctionner de manière autonome sur son propre collection de résultats intermédiaires. La façon dont chaque thread possède sa propre collection est d'appeler le
Collector.supplier()
qui est nécessaire pour retourner un nouveau collection à chaque fois.Ces collections de résultats intermédiaires sont ensuite fusionnés, à nouveau dans un thread-clos de la mode, jusqu'à ce qu'il n'y a qu'une seule collection. C'est le résultat final de la
collect()
opération.Quelques réponses de Balder et assylias ont suggéré l'utilisation d'
Collectors.toCollection()
et puis le passage d'un fournisseur qui retourne une liste existante au lieu d'une nouvelle liste. Cela viole l'obligation faite au fournisseur, qui de retour d'un nouveau, vide de collecte à chaque fois.Cela fonctionne pour les cas les plus simples, comme les exemples dans leurs réponses démontrent. Toutefois, il ne pourra pas, en particulier si le flux est exécuté en parallèle. (Une future version de la bibliothèque pourrait changer dans quelques imprévus de manière à la faire échouer, même dans le cas séquentiel.)
Prenons un exemple simple:
Quand je lance ce programme, je reçois souvent un
ArrayIndexOutOfBoundsException
. C'est parce que plusieurs threads d'exploitation surArrayList
, un fil-dangereux structure de données. OK, nous allons faire synchronisé:Ce n'échouent plus avec une exception. Mais au lieu de le résultat attendu:
il donne des résultats bizarres comme ceci:
C'est le résultat de la thread-clos de l'accumulation et la fusion des opérations que j'ai décrit ci-dessus. Avec un courant parallèle, chaque thread appelle le fournisseur pour obtenir sa propre collection pour l'intermédiaire de l'accumulation. Si vous passez un fournisseur qui renvoie la même collection, chaque thread ajoute ses résultats à la collection. Depuis il n'y a aucun ordre entre les threads, les résultats seront ajoutées dans certains ordre arbitraire.
Puis, lorsque ces intermédiaires collections sont fusionnés, ce essentiellement fusionne la liste avec lui-même. Les listes sont fusionnés à l'aide de
List.addAll()
, qui dit que les résultats ne sont pas définis si la collection source est modifié en cours de fonctionnement. Dans ce cas,ArrayList.addAll()
fait un tableau de l'opération de copie, de sorte qu'il finit par la duplication de lui-même, qui est une sorte de ce que l'on pourrait attendre, je suppose. (Notez que d'autres de la Liste des implémentations pourriez avoir complètement différent de comportement.) De toute façon, c'est ce qui explique l'étrange résultats et les éléments dupliqués dans la destination.Vous pourriez dire, "je vais juste assurez-vous d'exécuter mon flux de manière séquentielle" et aller de l'avant et d'écrire un code comme ceci
de toute façon. Je le recommande contre cela. Si vous contrôlez le flux de, sûr, vous pouvez garantir qu'il ne sera pas s'exécuter en parallèle. J'attends qu'un style de programmation émergent où les ruisseaux me remis autour de la place de collections. Si quelqu'un vous tend un ruisseau et vous utilisez ce code, il va échouer si le flux qui arrive à être parallèles. Pire, quelqu'un pourrait vous remettre un flux séquentiel et ce code fonctionne parfaitement pendant un certain temps, passer tous les tests, etc. Ensuite, certains arbitraire quantité de temps plus tard, le code ailleurs dans le système peut changer pour utiliser le flux parallèles qui va provoquer votre code à briser.
OK, alors assurez-vous de ne pas oublier d'appeler
sequential()
sur n'importe quel flux avant d'utiliser ce code:Bien sûr, vous vous en souviendrez pour ce faire, à chaque fois, non? 🙂 Disons que vous faites. Ensuite, la performance de l'équipe vont se demander pourquoi tous leurs soigneusement conçu en parallèle des implémentations ne fournissent pas toute speedup. Et encore une fois ils vont remonter vers votre code qui est de forcer l'ensemble du flux de déchets à exécuter séquentiellement.
Ne pas le faire.
toCollection
méthode doit retourner un nouveau et vide de collecte à chaque fois de me convaincre de ne pas. J'ai vraiment envie de casser la javadoc contrat de base des classes Java.forEachOrdered
. Les effets secondaires comprennent l'ajout d'éléments à une collection existante, indépendamment de savoir si elle a déjà des éléments. Si vous souhaitez un flux d'éléments placés dans un nouvelles collecte, à l'utilisationcollect(Collectors.toList())
outoSet()
outoCollection()
.Aussi loin que je peux voir, toutes les autres réponses jusqu'à présent utilisé un collecteur pour ajouter des éléments à un flux existant. Cependant, il existe une solution plus courte, et il fonctionne pour les séquentielle et parallèle des flux. Vous pouvez simplement utiliser la méthode forEachOrdered en combinaison avec une méthode de référence.
La seule restriction est que source et cible sont différentes listes, parce que vous n'êtes pas autorisé à apporter des modifications à la source d'un cours d'eau, tant qu'elle est traitée.
Noter que cette solution fonctionne pour les deux séquentiel et parallèle des flux. Cependant, il ne bénéficie pas de la simultanéité. La méthode de référence transmise à forEachOrdered seront toujours exécutées de façon séquentielle.
forEach(existing::add)
comme une possibilité dans un réponse de deux mois. J'aurais ajoutéforEachOrdered
ainsi...forEachOrdered
au lieu deforEach
?forEach
peut exécuter la fonction de l'objet en même temps pour flux parallèles. Dans ce cas, la fonction de l'objet doit être synchronisé correctement, par exemple en utilisant unVector<Integer>
.target::add
. Indépendamment de qui les threads de la méthode est invoquée, il n'y a pas de données de course. Je me serais attendu que vous le sachiez.ArrayList
, premier appelensureCapacity(source.length() + target.length())
sur la cible pour éviter le redimensionnement de la sauvegarde de tableau à plusieurs reprises (un problème uniquement si vous ajoutez un grand nombre d'éléments).La réponse courte est pas (ou devrait être pas). EDIT: oui, c'est possible (voir assylias réponse ci-dessous), mais continuez à lire. EDIT2: mais voir Stuart Marques de réponse pour encore une autre raison pourquoi vous toujours de ne pas le faire!
La plus longue réponse:
Le but de ces constructions dans Java 8 est de présenter quelques concepts de Programmation Fonctionnelle de la langue; dans la Programmation Fonctionnelle, les structures de données ne sont généralement pas modifié, au lieu de cela, de nouveaux sont créés à partir d'anciennes par le biais de transformations telles que la carte, filtre, plier/réduire et beaucoup d'autres.
Si vous doit modifier l'ancienne liste, il suffit de recueillir les éléments cartographiés dans une nouvelle liste:
et puis ne
list.addAll(newList)
— encore une fois: si vous devez vraiment.(ou de construction d'une nouvelle liste de la concaténation de l'ancien et du nouveau, et de réaffecter les
list
variable—c'est un peu plus dans l'esprit de la PF queaddAll
)Comme à l'API: même si l'API permet (de nouveau, voir assylias de réponse), vous devriez essayer d'éviter de faire que quel que soit, au moins en général. Il est préférable de ne pas lutter contre le paradigme (FP) et essayer d'apprendre plutôt que de le combattre (même si Java n'est généralement pas une langue FP), et de ne recourir à "sale" de tactique si absolument nécessaire.
La très longue réponse: (par exemple, si vous incluez l'effort de recherche et de lecture d'un FP intro/livre, comme l'a suggéré)
De trouver la raison de la modification de listes existantes est en général une mauvaise idée et conduit à moins maintenable code—sauf si vous êtes à la modification d'une variable locale et votre algorithme est court et/ou trivial, qui est hors de la portée de la question de la maintenabilité du code—trouver une bonne introduction à la Programmation Fonctionnelle (il y en a des centaines) et de démarrer la lecture. Un "aperçu" explication serait quelque chose comme: c'est plus mathématique et plus facile de raisonner sur de ne pas modifier les données (dans la plupart des parties de votre programme) et conduit à un niveau plus haut et moins technique (ainsi que, plus conviviale, une fois que votre cerveau transitions à l'écart de l'ancien style impératif de penser), les définitions de la logique du programme.
Erik Allik déjà donné de très bonnes raisons, pourquoi vous ne sera probablement pas vouloir recueillir des éléments d'un flux de données dans une Liste existante.
De toute façon, vous pouvez utiliser le one-liner suivant, si vous avez vraiment besoin de cette fonctionnalité.
Mais comme Stuart Marques l'explique dans sa réponse, vous devriez jamais ce faire, si le flux pourrait être en parallèle des flux - utilisation à vos risques et périls...
ConcurrentModificationException
.Vous suffit de consulter votre liste originale pour être celui que l'
Collectors.toList()
retourne.Voici une démo:
Et voici comment vous pouvez ajouter le nouvellement créé éléments à votre liste d'origine en une seule ligne.
C'est ce que ce Paradigme de la Programmation Fonctionnelle fournit.