Javadoc of Collector montre comment collecter des éléments d'un flux dans une nouvelle liste. Existe-t-il une ligne unique qui ajoute les résultats dans une ArrayList existante?
java
java-8
java-stream
collectors
codefx
la source
la source
Collection
"Réponses:
REMARQUE: la réponse de nosid montre comment ajouter à une collection existante en utilisant
forEachOrdered()
. C'est une technique utile et efficace pour faire muter des collections existantes. Ma réponse explique pourquoi vous ne devriez pas utiliser aCollector
pour muter une collection existante.La réponse courte est non , du moins, pas en général, vous ne devriez pas utiliser a
Collector
pour modifier une collection existante.La raison en est que les collecteurs sont conçus pour prendre en charge le parallélisme, même sur des collections qui ne sont pas thread-safe. Pour ce faire, chaque thread fonctionne indépendamment sur sa propre collection de résultats intermédiaires. La façon dont chaque thread obtient sa propre collection est d'appeler le
Collector.supplier()
qui est requis pour renvoyer une nouvelle collection à chaque fois.Ces collections de résultats intermédiaires sont ensuite fusionnées, à nouveau de manière confinée au thread, jusqu'à ce qu'il n'y ait qu'une seule collection de résultats. C'est le résultat final de l'
collect()
opération.Quelques réponses de Balder et d' assylias ont suggéré d'utiliser
Collectors.toCollection()
puis de transmettre un fournisseur qui renvoie une liste existante au lieu d'une nouvelle liste. Cela enfreint l'exigence du fournisseur, à savoir qu'il retourne une nouvelle collection vide à chaque fois.Cela fonctionnera pour des cas simples, comme le montrent les exemples de leurs réponses. Cependant, il échouera, en particulier si le flux est exécuté en parallèle. (Une future version de la bibliothèque pourrait changer d'une manière imprévue qui entraînerait son échec, même dans le cas séquentiel.)
Prenons un exemple simple:
List<String> destList = new ArrayList<>(Arrays.asList("foo")); List<String> newList = Arrays.asList("0", "1", "2", "3", "4", "5"); newList.parallelStream() .collect(Collectors.toCollection(() -> destList)); System.out.println(destList);
Lorsque j'exécute ce programme, j'obtiens souvent un fichier
ArrayIndexOutOfBoundsException
. Cela est dû au fait que plusieurs threads fonctionnent surArrayList
, une structure de données thread-unsafe. OK, faisons-le synchronisé:List<String> destList = Collections.synchronizedList(new ArrayList<>(Arrays.asList("foo")));
Cela n'échouera plus avec une exception. Mais au lieu du résultat attendu:
[foo, 0, 1, 2, 3]
cela donne des résultats étranges comme celui-ci:
[foo, 2, 3, foo, 2, 3, 1, 0, foo, 2, 3, foo, 2, 3, 1, 0, foo, 2, 3, foo, 2, 3, 1, 0, foo, 2, 3, foo, 2, 3, 1, 0]
C'est le résultat des opérations d'accumulation / fusion confinées au thread que j'ai décrites ci-dessus. Avec un flux parallèle, chaque thread appelle le fournisseur pour obtenir sa propre collection pour l'accumulation intermédiaire. Si vous passez un fournisseur qui renvoie la même collection, chaque thread ajoute ses résultats à cette collection. Puisqu'il n'y a aucun ordre parmi les threads, les résultats seront ajoutés dans un ordre arbitraire.
Ensuite, lorsque ces collections intermédiaires sont fusionnées, cela fusionne essentiellement la liste avec elle-même. Les listes sont fusionnées en utilisant
List.addAll()
, ce qui indique que les résultats ne sont pas définis si la collection source est modifiée pendant l'opération. Dans ce cas,ArrayList.addAll()
fait une opération de copie de tableau, donc il finit par se dupliquer, ce qui est en quelque sorte ce à quoi on pourrait s'attendre, je suppose. (Notez que d'autres implémentations de List peuvent avoir un comportement complètement différent.) Quoi qu'il en soit, cela explique les résultats étranges et les éléments dupliqués dans la destination.Vous pourriez dire: "Je vais juste m'assurer d'exécuter mon flux de manière séquentielle" et continuer et écrire un code comme celui-ci
en tous cas. Je recommande de ne pas faire cela. Si vous contrôlez le flux, bien sûr, vous pouvez garantir qu'il ne fonctionnera pas en parallèle. Je m'attends à ce qu'un style de programmation émerge où les flux sont transmis au lieu des collections. Si quelqu'un vous remet un flux et que vous utilisez ce code, il échouera si le flux se trouve être parallèle. Pire encore, quelqu'un pourrait vous remettre un flux séquentiel et ce code fonctionne très bien pendant un certain temps, passer tous les tests, etc. Ensuite, une certaine quantité arbitraire de temps plus tard, le code ailleurs dans le système pourrait changer d'utiliser des flux parallèles qui causeront votre code de casser.
OK, alors n'oubliez pas d'appeler
sequential()
sur n'importe quel flux avant d'utiliser ce code:Bien sûr, vous vous souviendrez de le faire à chaque fois, non? :-) Disons que vous faites. Ensuite, l'équipe de performance se demandera pourquoi toutes leurs implémentations parallèles soigneusement conçues ne fournissent aucune accélération. Et une fois de plus, ils le retraceront jusqu'à votre code, ce qui oblige tout le flux à s'exécuter de manière séquentielle.
Ne fais pas ça.
la source
toCollection
méthode renvoie une nouvelle collection vide à chaque fois me convainc de ne pas le faire. Je veux vraiment briser le contrat javadoc des classes Java de base.forEachOrdered
. Les effets secondaires incluent l'ajout d'éléments à une collection existante, qu'elle en ait déjà ou non. Si vous souhaitez que les éléments d'un flux soient placés dans une nouvelle collection, utilisezcollect(Collectors.toList())
outoSet()
outoCollection()
.Pour autant que je sache, toutes les autres réponses jusqu'à présent utilisaient un collecteur pour ajouter des éléments à un flux existant. Cependant, il existe une solution plus courte et elle fonctionne pour les flux séquentiels et parallèles. Vous pouvez simplement utiliser la méthode forEachOrdered en combinaison avec une référence de méthode.
La seule restriction est que la source et la cible sont des listes différentes, car vous n'êtes pas autorisé à apporter des modifications à la source d'un flux tant qu'il est traité.
Notez que cette solution fonctionne pour les flux séquentiels et parallèles. Cependant, il ne bénéficie pas de la concurrence. La référence de méthode passée à forEachOrdered sera toujours exécutée séquentiellement.
la source
forEach(existing::add)
comme possibilité dans un réponse il y a deux mois . J'aurais dû ajouterforEachOrdered
aussi…forEachOrdered
au lieu deforEach
?forEachOrdered
fonctionne pour les flux séquentiels et parallèles . En revanche,forEach
peut exécuter l'objet de fonction passé simultanément pour les flux parallèles. Dans ce cas, l'objet de fonction doit être correctement synchronisé, par exemple en utilisant unVector<Integer>
.target::add
. Quels que soient les threads à partir desquels la méthode est appelée, il n'y a pas de course aux données . Je m'attendais à ce que vous le sachiez.La réponse courte est non (ou devrait être non). EDIT: oui, c'est possible (voir la réponse d'assylias ci-dessous), mais continuez à lire. EDIT2: mais voyez la réponse de Stuart Marks pour une autre raison pour laquelle vous ne devriez toujours pas le faire!
La réponse la plus longue:
Le but de ces constructions en Java 8 est d'introduire certains concepts de programmation fonctionnelle dans le langage; dans la programmation fonctionnelle, les structures de données ne sont généralement pas modifiées, au lieu de cela, de nouvelles sont créées à partir des anciennes au moyen de transformations telles que mapper, filtrer, replier / réduire et bien d'autres.
Si vous devez modifier l'ancienne liste, rassemblez simplement les éléments mappés dans une nouvelle liste:
final List<Integer> newList = list.stream() .filter(n -> n % 2 == 0) .collect(Collectors.toList());
puis faites
list.addAll(newList)
- à nouveau: si vous devez vraiment(ou construisez une nouvelle liste concaténant l'ancienne et la nouvelle, et attribuez-la au
list
variable - c'est un peu plus dans l'esprit de FP queaddAll
)Quant à l'API: même si l'API le permet (encore une fois, voir la réponse d'assylias), vous devriez essayer d'éviter de faire cela quoi qu'il en soit, du moins en général. Il est préférable de ne pas combattre le paradigme (FP) et d'essayer de l'apprendre plutôt que de le combattre (même si Java n'est généralement pas un langage FP), et de ne recourir à des tactiques «plus sales» que si c'est absolument nécessaire.
La réponse très longue: (c'est-à-dire si vous incluez l'effort de trouver et de lire une intro / livre de PF comme suggéré)
Découvrir pourquoi la modification de listes existantes est en général une mauvaise idée et conduit à un code moins maintenable - à moins que vous ne modifiiez une variable locale et que votre algorithme soit court et / ou trivial, ce qui est hors de la portée de la question de la maintenabilité du code —Trouvez une bonne introduction à la programmation fonctionnelle (il y en a des centaines) et commencez à lire. Une explication "aperçu" serait quelque chose comme: il est plus mathématiquement sain et plus facile à raisonner de ne pas modifier les données (dans la plupart des parties de votre programme) et conduit à un niveau plus élevé et moins technique (ainsi que plus convivial pour les humains, une fois votre cerveau transitions loin de la pensée impérative à l'ancienne) définitions de la logique du programme.
la source
Erik Allik a déjà donné de très bonnes raisons, pourquoi vous ne voudrez probablement pas collecter les éléments d'un flux dans une liste existante.
Quoi qu'il en soit, vous pouvez utiliser le one-liner suivant, si vous avez vraiment besoin de cette fonctionnalité.
Mais comme l' explique Stuart Marks dans sa réponse, vous ne devriez jamais faire cela, si les flux peuvent être des flux parallèles - utilisez à vos propres risques ...
la source
Il vous suffit de vous référer à votre liste d'origine pour être celle que le
Collectors.toList()
retourne.Voici une démo:
import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class Reference { public static void main(String[] args) { List<Integer> list = Arrays.asList(1, 2, 3, 4, 5); System.out.println(list); // Just collect even numbers and start referring the new list as the original one. list = list.stream() .filter(n -> n % 2 == 0) .collect(Collectors.toList()); System.out.println(list); } }
Et voici comment ajouter les éléments nouvellement créés à votre liste d'origine en une seule ligne.
List<Integer> list = ...; // add even numbers from the list to the list again. list.addAll(list.stream() .filter(n -> n % 2 == 0) .collect(Collectors.toList()) );
C'est ce que propose ce paradigme de programmation fonctionnelle.
la source
Je concaténerais l'ancienne liste et la nouvelle liste en tant que flux et enregistrerais les résultats dans la liste de destination. Fonctionne bien en parallèle aussi.
J'utiliserai l'exemple de réponse acceptée donnée par Stuart Marks:
List<String> destList = Arrays.asList("foo"); List<String> newList = Arrays.asList("0", "1", "2", "3", "4", "5"); destList = Stream.concat(destList.stream(), newList.stream()).parallel() .collect(Collectors.toList()); System.out.println(destList); //output: [foo, 0, 1, 2, 3, 4, 5]
J'espère que cela aide.
la source
Disons que nous avons une liste existante, et que nous allons utiliser java 8 pour cette activité `
import java.util.*; import java.util.stream.Collectors; public class AddingArray { public void addArrayInList(){ List<Integer> list = Arrays.asList(3, 7, 9); // And we have an array of Integer type int nums[] = {4, 6, 7}; //Now lets add them all in list // converting array to a list through stream and adding that list to previous list list.addAll(Arrays.stream(nums).map(num -> num).boxed().collect(Collectors.toList())); } }
»
la source
targetList = sourceList.stream().flatmap(List::stream).collect(Collectors.toList());
la source