| 

.NET C# Java Javascript Exception

2
Ich möchte in Java über eine Collection (hier List) mit einer for-Schleife (die Java1.5-Version davon) iterieren und bestimmte Elemente aus ihr entfernen, wenn ich gerade auf das Element gestoßen bin.

Was passiert dann aber mit der Schleife, wenn ich das Element entferne?
Wird das tatsächlich nächste Element (das was ohne Entfernung als nächste dran gewesen wäre) genommen? Wird dieses übersprungen (mit einer hochgezählten Indexvariablen würde das passieren)? Bricht die Schleife ab, obwohl noch Elemente zu iterieren wären?

Welche effiziente andere Lösung gäbe es, wenn eine der letzten beiden Möglichkeiten zutrifft?


Zur Veranschaulichung:
List<String> stringList = new ArrayList<String>();
... //befüllen der Liste
for(String str: stringList) {
if (str.equals("hallo"))
stringList.remove(str);
}


Christoph
09.09.2009
pocketdevel 363 1 2 6
8 Antworten
1
tut mir leid wegen der vielen falschen antworten.

bei allen bisher vorgeschlagenen lösungen wird eine ConcurrentModificationException geworfen.

um das zu erreichen - iterieren und bei bedarf das aktuelle element zu entfernen, sollte man den iterator explizit verwenden, und die dazugehörige .remove() methode.

die implementierung der liste - arraylist vs linkedlist ist im grunde egal, man sollte jedoch beachten das das entfernen aus der mitte einer arraylist eine laufzeit von O(n) hat. eventuell ist eine linkedList dann besser - dort ist das entfernen aus der mitte nur O(1)

List<String> blubb = new LinkedList<String>();
blubb.add("abc");
blubb.add("def");
Iterator<String> it = blubb.iterator();
while(it.hasNext()){
String current = it.next();
if (current.equals("abc")){
it.remove();
}
09.09.2009
Andreas 36 1
[code]import java.util.ArrayList;
import java.util.List;

public class Main {
public static void main(String[] args) {
List<String> stringList = new ArrayList<String>();
stringList.add("Hallo");
stringList.add("Andreas");
stringList.add("FOOBAR");
stringList.add(":)");

for(String str: new ArrayList<String>(stringList)) {
if (str.equals("FOOBAR"))
stringList.remove(str);
}

System.out.println(stringList);
}
}[/code] erzeugt keine ConcurrentModificationException... ;)
cowabunga1984 09.09.2009
in dem fall nicht, aber es wird unnötigerweise die liste mindestens 2x iteriert.
Andreas 10.09.2009
Die Lösung mit den Iteratoren ist gut, schade nur dass das for-Konstrukt aus Java1.5 keinen Zugriff auf den Iterator erlaubt, denn das for wird ja in eine Iteratorschleife umgewandelt.

Danke für den Hinweis mit der LinkedList. Ich vergesse die immer wieder, obwohl die grad für mein Programm die beste Implementierung wäre.

@cowabunga1984: Du umgehst das Problem in dem du die Liste kopierst auf dieser iterierst und im Original löschst. Ist nicht so ganz effizient, wenn man ein paar tausend Objekte in der Liste hätte, aber würde durchlaufen. Dass 2x Iterieren sehe ich allerdings nicht...
pocketdevel 10.09.2009
Bei mir spielt die Performance oft eher eine untergeordnete Rolle. Meistens habe ich mich deshalb für die "kürzere" Variante entschieden. Falls es auf Performance ankommt, ist natürlich die Iterator-Lösung die bessere...
cowabunga1984 10.09.2009
2
Hallo,

EDIT: Hatte leider etwas zu viel Code aus der Antwort von cowabunga (die Liste vor dem iterieren kopieren) kopiert, so das alles was ich in der Original-Version sagte unnütz war. Hier die korrigierte Version (danke auch an Andreas für den subtilen Hinweis *g*). Es gibt wohl keine andere Wahl als den Iterator direkt zu verwenden.

import java.util.*;

public final class RandomListTest {
public static void main(String[] args) {
List<String> stringList =
new ArrayList<String>(Arrays.asList("a", "hallo", "b"));

for(Iterator<String> i=stringList.iterator(); i.hasNext();) {
String str = i.next();
System.out.printf("found element: %s\n", str);
if (str.equals("hallo")) {
i.remove();
}
}

System.out.println(stringList);
}
}



Die Ausgabe ist dann so:
found element: a
found element: hallo
found element: b
[a,b]
09.09.2009
weissi 101 1 2
weissi 101 1 2
2
in der lösung sind 3 fehler versteckt.
Andreas 09.09.2009
2
Du könntest statt einer foreach Schleife eine fori Schleife nutzen und die Collection von hinten beginnend durchlaufen.

List<String> stringList = new ArrayList<String>();
... //befüllen der Liste
for(int i = length - 1; i > -1; i--)
if (str.get(i).equals("hallo"))
stringList.remove(i);
09.09.2009
BenR 41 2
Die Lösung scheint was Speicherverbrauch angeht, die beste zu sein, nur bei der Laufzeit muss man darauf achten, welche List-Implementierung genommen wird. ArrayList und Vector erlauben Wahlfreienzugriff mit konstanter Laufzeit (ungefähr o(1)), aber LinkedList lineare ( o(n) ).
pocketdevel 10.09.2009
PS: Die Variante mit dem Iterator ist natürlich gleich gut beim Speicherverbrauch.
pocketdevel 10.09.2009
1
Zur ursprünglichen Frage: Warum überhaupt selbst iterieren? Die Listen Implementierungen können das alle selbst, vorausgesetzt die Objekte der Liste implementieren equals() richtig, was aber im Fall der String Liste gegeben ist.

List<String> stringList = new ArrayList<String>();
... //befüllen der Liste
stringList.remove("hallo");

Oder, falls das Fragliche Objekt mehrfach vorkommt:

List<String> stringList = new ArrayList<String>();
... //befüllen der Liste
while(stringList.remove("hallo"))
{}
29.09.2009
keinhaar 210 2 6
0
Ich hätt's mir auch denken können: es wird eine Exception geworfen(java.util.ConcurrentModificationException), weil die Implementierungen von ArrayList und Vector kein gleichzeitiges Lesen und Verändern zulassen, aber genau das wird in der Schleife getan: alle Elemente der Reihe nach lesen und in die Variable "str" schreiben, während das remove() die Liste verändert.
09.09.2009
pocketdevel 363 1 2 6
0
Falls Du Elemente aus der List löschen möchtest kannst Du es so implementieren:

List<String> stringList = new ArrayList<String>();
... //befüllen der Liste
for(String str: new LinkedList<String>(stringList)) {
if (str.equals("hallo"))
stringList.remove(str);
}
09.09.2009
cowabunga1984 78 1 5
Geht natürlich, aber die Effizienz ist nicht optimal (2. Liste und mit jedem stringList.remove(str) muss je nach Implementierung womöglich die gesamte Liste nach str nochmal durchsucht werden).
pocketdevel 21.09.2009
0
Natürlich geht das nicht, dürfte in keiner Programmiersprache gehen meines Wissens.
Als alternative könntest du die Liste clonen oder eine LinkedList nehmen.

Grüßle

PS: ups da war cowabunga schneller...sorry habs nicht gesehen.
09.09.2009
Scout 1,4k 2 8
Scout 1,4k 2 8
das stimmt nicht, siehe mein Beitrag
weissi 09.09.2009
0
Eine Lösung wäre noch, dass man in einer neuen Liste alle Elemente im ersten Schleifendurchlauf sammelt und diese anschließend mit removeAll() alle auf einmal entfernt.

List<String> stringList = new ArrayList<String>();
... //befüllen der Liste
List<String> toRemove = new ArrayList<String>();
for(String str: stringList) {
if (str.equals("hallo"))
toRemove.add(str);
}
stringList.removeAll(toRemove);

Effizient dürfte aber jenseits von Gut und Böse liegen. Erstens wird eine neue Liste angelegt (Speicherverbrauch), zweitens wird die Liste wahrscheinlich (je nach List-Implementierung) mindestens 2x durchlaufen (removeAll() muss ja auch noch die Elemnte finden, sind ja Werte und keine Indexe in der toRemove-Liste gegeben).
10.09.2009
pocketdevel 363 1 2 6

Stelle deine Java-Frage jetzt!