Modification d'objets dans le flux en Java8 lors de l'itération

87

Dans les flux Java8, suis-je autorisé à modifier / mettre à jour des objets à l'intérieur? Pour par exemple. List<User> users:

users.stream().forEach(u -> u.setProperty("value"))
Tejas Gokhale
la source

Réponses:

101

Oui, vous pouvez modifier l'état des objets à l'intérieur de votre flux, mais le plus souvent, vous devez éviter de modifier l'état de la source du flux. De la section non-interférence de la documentation du package de flux, nous pouvons lire que:

Pour la plupart des sources de données, éviter les interférences signifie s'assurer que la source de données n'est pas du tout modifiée pendant l'exécution du pipeline de flux. L'exception notable à cela concerne les flux dont les sources sont des collections simultanées, spécifiquement conçues pour gérer les modifications simultanées. Les sources de flux simultanées sont celles dont les Spliteratorrapports la CONCURRENTcaractéristique.

Alors ça va

  List<User> users = getUsers();
  users.stream().forEach(u -> u.setProperty(value));
//                       ^    ^^^^^^^^^^^^^

mais ce n'est pas dans la plupart des cas

  users.stream().forEach(u -> users.remove(u));
//^^^^^                       ^^^^^^^^^^^^

et peut lancer ConcurrentModificationExceptionou même d'autres exceptions inattendues comme NPE:

List<Integer> list = IntStream.range(0, 10).boxed().collect(Collectors.toList());

list.stream()
    .filter(i -> i > 5)
    .forEach(i -> list.remove(i));  //throws NullPointerException
Pshemo
la source
4
Quelles sont les solutions si vous souhaitez modifier les utilisateurs?
Augustas
2
@Augustas Tout dépend de la manière dont vous souhaitez modifier cette liste. Mais en général, vous devriez éviter Iteratorce qui empêche de modifier la liste (supprimer / ajouter de nouveaux éléments) pendant l'itération et que les flux et les boucles for-each l'utilisent. Vous pouvez essayer d'utiliser une boucle simple comme celle for(int i=0; ..; ..)qui n'a pas ce problème (mais ne vous arrêtera pas quand un autre thread modifiera votre liste). Vous pouvez également utiliser des méthodes telles que list.removeAll(Collection), list.removeIf(Predicate). Les java.util.Collectionsclasses ont également quelques méthodes qui pourraient être utiles comme addAll(CollectionOfNewElements,list).
Pshemo
@Pshemo, une solution à cela est de créer une nouvelle instance d'une Collection comme ArrayList avec les éléments à l'intérieur de votre liste principale; parcourez la nouvelle liste et effectuez l'opération sur la liste principale: new ArrayList <> (users) .stream.forEach (u -> users.remove (u));
MNZ
2
@Blauhirn Résoudre quoi? Nous ne sommes pas autorisés à modifier la source du flux lors de l'utilisation de flux, mais sommes autorisés à modifier l'état de ses éléments. Peu importe que nous utilisions map, foreach ou d'autres méthodes de flux.
Pshemo du
1
Au lieu de modifier la collection, je suggérerais d'utiliserfilter()
jontro
5

La manière fonctionnelle serait à mon humble avis:

import static java.util.stream.Collectors.toList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

public class PredicateTestRun {

    public static void main(String[] args) {

        List<String> lines = Arrays.asList("a", "b", "c");
        System.out.println(lines); // [a, b, c]
        Predicate<? super String> predicate = value -> "b".equals(value);
        lines = lines.stream().filter(predicate.negate()).collect(toList());

        System.out.println(lines); // [a, c]
    }
}

Dans cette solution, la liste d'origine n'est pas modifiée, mais doit contenir votre résultat attendu dans une nouvelle liste accessible sous la même variable que l'ancienne

Andreas M. Oberheim
la source
3

Pour faire une modification structurelle sur la source du flux, comme Pshemo l'a mentionné dans sa réponse, une solution est de créer une nouvelle instance d'un Collectionlike ArrayListavec les éléments à l'intérieur de votre liste principale; parcourez la nouvelle liste et effectuez les opérations sur la liste principale.

new ArrayList<>(users).stream().forEach(u -> users.remove(u));
MNZ
la source
1

Pour se débarrasser de ConcurrentModificationException, utilisez CopyOnWriteArrayList

Krupali_wadekar
la source
2
C'est en fait correct, mais: cela dépend de la classe spécifique qui est utilisée ici. Mais quand vous savez seulement que vous avez un List<Something>- vous n'avez aucune idée s'il s'agit d'un CopyOnWriteArrayList. Donc, cela ressemble plus à un commentaire médiocre, mais pas à une vraie réponse.
GhostCat
S'il l'avait posté sous forme de commentaire, quelqu'un lui aurait probablement dit qu'il ne devrait pas publier de réponses sous forme de commentaires.
Bill K
1

Au lieu de créer des choses étranges, vous pouvez juste filter()et après map()votre résultat.

C'est beaucoup plus lisible et sûr. Les flux le feront en une seule boucle.

Nicolas Zozol
la source
1

Oui, vous pouvez modifier ou mettre à jour les valeurs des objets de la liste dans votre cas de la même manière:

users.stream().forEach(u -> u.setProperty("some_value"))

Cependant, l'instruction ci-dessus apportera des mises à jour sur les objets source. Ce qui peut ne pas être acceptable dans la plupart des cas.

Heureusement, nous avons un autre moyen comme:

List<Users> updatedUsers = users.stream().map(u -> u.setProperty("some_value")).collect(Collectors.toList());

Ce qui renvoie une liste mise à jour, sans gêner l'ancienne.

Gaurav Khanzode
la source
0

Comme mentionné précédemment, vous ne pouvez pas modifier la liste d'origine, mais vous pouvez diffuser, modifier et collecter des éléments dans une nouvelle liste. Voici un exemple simple de modification d'un élément de chaîne.

public class StreamTest {

    @Test
    public void replaceInsideStream()  {
        List<String> list = Arrays.asList("test1", "test2_attr", "test3");
        List<String> output = list.stream().map(value -> value.replace("_attr", "")).collect(Collectors.toList());
        System.out.println("Output: " + output); // Output: [test1, test2, test3]
    }
}
Vadim
la source
0

Vous pouvez utiliser removeIfpour supprimer des données d'une liste de manière conditionnelle.

Par exemple: - Si vous souhaitez supprimer tous les nombres pairs d'une liste, vous pouvez le faire comme suit.

    final List<Integer> list = IntStream.range(1,100).boxed().collect(Collectors.toList());

    list.removeIf(number -> number % 2 == 0);
JJ
la source
-1

Cela pourrait être un peu tard. Mais voici l'un des usages. Ceci pour obtenir le décompte du nombre de fichiers.

Créez un pointeur vers la mémoire (un nouvel obj dans ce cas) et faites modifier la propriété de l'objet. Le flux Java 8 ne permet pas de modifier le pointeur lui-même et, par conséquent, si vous déclarez simplement compter comme une variable et essayez d'incrémenter dans le flux, cela ne fonctionnera jamais et lèvera une exception du compilateur en premier lieu

Path path = Paths.get("/Users/XXXX/static/test.txt");



Count c = new Count();
            c.setCount(0);
            Files.lines(path).forEach(item -> {
                c.setCount(c.getCount()+1);
                System.out.println(item);});
            System.out.println("line count,"+c);

public static class Count{
        private int count;

        public int getCount() {
            return count;
        }

        public void setCount(int count) {
            this.count = count;
        }

        @Override
        public String toString() {
            return "Count [count=" + count + "]";
        }



    }
Joey587
la source