Ändern von Objekten im Stream in Java8 während der Iteration

87

Darf ich in Java8-Streams Objekte in ändern / aktualisieren? Zum Beispiel. List<User> users::

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

Antworten:

100

Ja, Sie können den Status von Objekten in Ihrem Stream ändern. In den meisten Fällen sollten Sie jedoch den Status der Quelle des Streams nicht ändern . Aus dem Abschnitt über die störungsfreie Dokumentation des Stream-Pakets können wir Folgendes lesen:

Für die meisten Datenquellen bedeutet das Verhindern von Interferenzen, dass sichergestellt wird, dass die Datenquelle während der Ausführung der Stream-Pipeline überhaupt nicht geändert wird . Die bemerkenswerte Ausnahme bilden Streams, deren Quellen gleichzeitige Sammlungen sind, die speziell für die gleichzeitige Änderung entwickelt wurden. Gleichzeitige Stream-Quellen sind solche, deren SpliteratorBerichte das CONCURRENTMerkmal sind.

Das ist also in Ordnung

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

Dies ist jedoch in den meisten Fällen nicht der Fall

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

und kann ConcurrentModificationExceptionoder sogar andere unerwartete Ausnahmen wie NPE werfen :

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
quelle
4
Was sind Lösungen, wenn Sie Benutzer ändern möchten?
Augustas
2
@ August Es hängt alles davon ab, wie Sie diese Liste ändern möchten. Im Allgemeinen sollten Sie jedoch vermeiden Iterator, dass das Ändern der Liste (Entfernen / Hinzufügen neuer Elemente) während der Iteration verhindert wird und sowohl Streams als auch for-each-Schleifen diese verwenden. Sie könnten versuchen, eine einfache Schleife zu for(int i=0; ..; ..)verwenden, die dieses Problem nicht hat (aber Sie nicht aufhält, wenn ein anderer Thread Ihre Liste ändert). Sie könnten auch Methoden wie list.removeAll(Collection), list.removeIf(Predicate). Auch java.util.CollectionsKlasse haben wenige Methoden, die nützlich sein könnten addAll(CollectionOfNewElements,list).
Pshemo
@Pshemo, eine Lösung hierfür besteht darin, eine neue Instanz einer Sammlung wie ArrayList mit den Elementen in Ihrer Primärliste zu erstellen. Durchlaufen Sie die neue Liste und führen Sie die Operation für die primäre Liste aus: new ArrayList <> (Benutzer) .stream.forEach (u -> users.remove (u));
MNZ
2
@Blauhirn Was lösen? Wir dürfen die Quelle des Streams nicht ändern, während wir den Stream verwenden, aber wir dürfen den Status seiner Elemente ändern. Es spielt keine Rolle, ob wir Map-, Foreach- oder andere Stream-Methoden verwenden.
Pshemo
1
Anstatt die Sammlung zu filter()
ändern,
5

Der funktionale Weg wäre imho:

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]
    }
}

In dieser Lösung wird die ursprüngliche Liste nicht geändert, sondern sollte Ihr erwartetes Ergebnis in einer neuen Liste enthalten, auf die unter derselben Variablen wie die alte zugegriffen werden kann

Andreas M. Oberheim
quelle
3

Um strukturelle Änderungen an der Quelle des Streams vorzunehmen, wie Pshemo in seiner Antwort erwähnt hat, besteht eine Lösung darin, eine neue Instanz eines CollectionLike ArrayListmit den Elementen in Ihrer primären Liste zu erstellen . Durchlaufen Sie die neue Liste und führen Sie die Operationen für die primäre Liste aus.

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

Verwenden Sie CopyOnWriteArrayList , um ConcurrentModificationException zu entfernen

Krupali_wadekar
quelle
2
Das ist eigentlich richtig, aber: Dies hängt von der spezifischen Klasse ab, die hier verwendet wird. Aber wenn Sie nur wissen, dass Sie eine List<Something>haben , haben Sie keine Ahnung, ob dies eine CopyOnWriteArrayList ist. Das ist also eher ein mittelmäßiger Kommentar, aber keine echte Antwort.
GhostCat
Wenn er es als Kommentar gepostet hätte, hätte ihm höchstwahrscheinlich jemand gesagt, dass er Antworten nicht als Kommentare posten sollte.
Bill K
1

Anstatt seltsame Dinge zu erschaffen, können Sie einfach filter()und dann map()Ihr Ergebnis erzielen.

Dies ist viel lesbarer und sicherer. Streams schaffen es in nur einer Schleife.

Nicolas Zozol
quelle
0

Wie bereits erwähnt, können Sie die ursprüngliche Liste nicht ändern, aber Sie können Elemente streamen, ändern und in einer neuen Liste sammeln. Hier ist ein einfaches Beispiel zum Ändern eines Zeichenfolgenelements.

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
quelle
0

Sie können das verwenden removeIf, um Daten bedingt aus einer Liste zu entfernen.

Beispiel: - Wenn Sie alle geraden Zahlen aus einer Liste entfernen möchten, können Sie dies wie folgt tun.

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

    list.removeIf(number -> number % 2 == 0);
JJ
quelle
0

Ja, Sie können die Werte der Objekte in der Liste in Ihrem Fall ebenfalls ändern oder aktualisieren:

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

Mit der obigen Anweisung werden jedoch Aktualisierungen an den Quellobjekten vorgenommen. Was in den meisten Fällen möglicherweise nicht akzeptabel ist.

Zum Glück haben wir einen anderen Weg wie:

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

Dadurch wird eine aktualisierte Liste zurückgegeben, ohne die alte zu beeinträchtigen.

Gaurav Khanzode
quelle
-1

Dies könnte etwas spät sein. Aber hier ist eine der Verwendung. Dies, um die Anzahl der Dateien zu ermitteln.

Erstellen Sie einen Zeiger auf den Speicher (in diesem Fall ein neues Objekt) und ändern Sie die Eigenschaft des Objekts. Der Java 8-Stream erlaubt es nicht, den Zeiger selbst zu ändern. Wenn Sie also nur als Variable zählen und versuchen, innerhalb des Streams zu inkrementieren, funktioniert dies niemals und löst überhaupt keine Compiler-Ausnahme aus

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
quelle