Java 8 stream API: Ausnahmen beim ändern von Listen
Nehmen wir ein ArrayList
und füllen Sie es mit etwas einfachem:
List<String> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(""+i);
}
Werde ich versuchen, Sie zu entfernen ein Mitglied, sagen wir namens 5, mit verschiedenen Möglichkeiten der stream-API. Für diese definiere ich die Methode, die mir eine ConcurentModificationException
bei der Verwendung von traditionellen iteration mit iterator.
void removeMember(String clientListener) {
list.remove(clientListener);
}
Dieser code gibt mir die Ausnahme, die ich verstehen:
list.parallelStream()
.filter(string -> string.equalsIgnoreCase("5"))
.forEach(string -> removeMember(string));
Allerdings versucht nur stream()
, nicht parallelStream()
gibt einen null-Zeiger-Ausnahme (NPE), die ist seltsam für mich:
list.stream()
.filter(string -> string.equalsIgnoreCase("5"))
.forEach(string -> removeMember(string));
Ändern Sie nun den List
Typ LinkedList<>
. Letzte code mit stream()
gibt mir ein ConcurentModificationException
, und parallelStream()
plötzlich funktioniert!
So, die Fragen stellen.
-
Ist die interne
parallelStream()
Küche (Spliterators und anderer Magie) smart genug, um solche element entfernen fürLinkedList
? Wird es immer? -
Warum war das NPE für
ArrayList
? Warum NPE, nichtConcurentModificationException
ich meine.
- Durch die Art und Weise, Sie könnte verwenden
Integer.toString(i)
statt""+i
- 4-mal länger((
- Ich wäre nicht überrascht, wenn Sie könnte erheblich zur Verbesserung der Geschwindigkeit als Ergebnis.
- Wenn Sie möchten, entfernen von Elementen aus einer Liste, entspricht ein Prädikat, verwenden Sie
List.removeIf
.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Das Verhalten Ihres Codes ist im wesentlichen unverändert (daher auch die verschiedenen Antworten, die Sie bekommen). Die Bach-Dokumentation (Nicht-Intereference Abschnitt) Staaten:
Sowie
ArrayList
undLinkedList
sind nicht gleichzeitigen.Könnte man eine gleichzeitige Quelle, aber es würde mehr Sinn machen, sich zu bewegen Weg von ändern der Quelle des Stroms, beispielsweise durch den Einsatz von
Collection#removeIf
:Hinzufügen von ein paar debug prints auf der pipeline zeigt, die Quelle der NullPointerException:
Dieser Ausgänge:
Wenn die "5" wurde entfernt aus der Liste alle Elemente von "6" bis "9" wurden verschoben eine position nach Links (D. H. Ihre Indizes wurden erniedrigt, indem Sie 1). Die Stream-pipeline trifft es nicht erkennen, so dass es übersprungen "6", und wenn es verarbeitet die Letzte position (die ursprünglich enthaltenen "9"), traf er auf null, was dazu führte
NullPointerException
wennstring.equalsIgnoreCase("5")
wurde evaluiert für Sie.Dies ist ähnlich zu dem, was Sie erhalten würde, in diesem traditionellen
for
Schleife:Nur hier würden Sie bekommen
IndexOutOfBoundsException
stattNullPointerException
, dalist.get(i)
würde fehlschlagen, wenni==9
. Ich denke, der Stream-pipeline arbeitet direkt auf dem internen array desArrayList
, damit es nicht zu erkennen, dass die Größe der Liste geändert hat.EDIT:
Folgenden Holger ' s Kommentar, ich änderte den code zu eliminieren, der
NullPointerException
(durch ändern der filter auffilter(string -> "5".equalsIgnoreCase(string))
). Diese in der Tat produziertConcurrentModificationException
:Iterator
. Was macht den Unterschied ist, dass dieforEach
Betrieb verfügt über eine spezielle Implementierung benutzt eine lokale Kopie der Größe und auch nicht, überprüfen Sie diemodCount
bei jeder iteration aber nur einmal nach der Schleife. Hatte nicht dieNullPointerException
beendet die Schleife, eineConcurrentModificationException
geworfen wurde direkt nach der Schleife. Mit einer lokalen Kopie die Größe ist eine Folge der Aufspaltung unterstützen, da jedes Stück hat seinen eigenen index und Größe.Wenn Sie verwenden möchten, streams, statt einer änderung der ursprünglichen Sammlung (siehe Unveränderlichkeit mit seiner inhärenten thread-Sicherheit), sollten Sie nur abrufen, eine neue Liste ohne dieses element:
Bezüglich deiner anderen Frage über
parallelStream
und wenn dieser Ansatz könnte auch immer?Nein, es wird definitiv nicht. Die
Lists
Sie verwenden, sind nicht gebaut, um die Unterstützung der gleichzeitige Zugriff, manchmal wird es scheinen, zu arbeiten, zu anderen Zeiten wird es scheitern, als Sie sah, oder geben Sie "unerwartete" Ergebnisse. Wenn Sie wissen, dass die Daten in einer Datenstruktur zugegriffen wird, die von mehreren thread immer code entsprechend.Während mit Java8-Lambdas, Ihr bestes, um nicht den stack-trace auf seinem Gesicht Wert.Der Fehler sollte ausgelesen werden um zu verstehen, dass eine NPE ist verursacht durch einige code-Zeile innerhalb der
forEach
lambda.So, Sie brauchen, um zu bewerten in jeder Zeile und sehen, was kann die Ursache sein.