PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : [Java] Collections und Elemente löschen


Monger
2005-11-02, 16:08:43
Hallo,

ich hab wiedermal eine abgedrehte Java Frage...
Ich habe eine Liste (um genau zu sein: eine ArrayList) mit irgendwelchen Objekten. Jetzt möchte ich aus dieser Liste bestimmte Objekte entfernen. Das sieht ungefähr so aus:



for(Ding ding : sammlung){
If(!ding.isValid()){
sammlung.remove(ding);
}
}


Dabei kriege ich aber regelmäßig eine "ConcurrentModificationException". Ich denke mir das so, dass ein "remove" eine Veränderung an der Liste darstellt, und wenn das nächste Mal der Iterator auf der Liste nachschaut sieht er am Zähler der Veränderungen, dass sich etwas an der Liste getan hat, und wirft eben diese Exception.

Natürlich kann ich das Problem umgehen, indem ich alle "schlechten" Elemente in eine zweite Liste einfüge, und nach dem Schleifendurchlauf die eine Liste von der anderen entferne.

Jetzt habe ich aber mal in der API nachgelesen(http://java.sun.com/j2se/1.5.0/docs/api/java/util/Iterator.html), und da steht, dass der Iterator eine Methode besitzt, um das gerade bearbeitete Element aus der Collection zu schmeißen. Da die for...each Schleife ja afaik auf dem Iterator aufbaut, frage ich mich, ob es da nicht doch eine direkte, elegante Möglichkeit gibt ?!?

HajottV
2005-11-02, 17:43:08
...

Hallo,

das ist genau eine Situation, in der das for Construkt keinen Vorteil bietet. Das remove() kannst Du nur am Iterator aufrufen - den hast Du aber nicht in der Hand.

Gruß

Jörg

HellHorse
2005-11-02, 20:08:07
Dabei kriege ich aber regelmäßig eine "ConcurrentModificationException".
Die solltest du immer kriegen, wenn du eine Collection modifizierst während du über die iterierst.

Ich denke mir das so, dass ein "remove" eine Veränderung an der Liste darstellt, und wenn das nächste Mal der Iterator auf der Liste nachschaut sieht er am Zähler der Veränderungen, dass sich etwas an der Liste getan hat, und wirft eben diese Exception.
Genauso ist es.

Jetzt habe ich aber mal in der API nachgelesen(http://java.sun.com/j2se/1.5.0/docs/api/java/util/Iterator.html), und da steht, dass der Iterator eine Methode besitzt, um das gerade bearbeitete Element aus der Collection zu schmeißen.
Gibt's, das Problem ist nur diese Operation ist optional. Das heisst du findest erst zur Laufzeit raus, ob das Objekt diese Methode unterstützt. Soviel zum Thema statisches Typsystem in Java.

Da die for...each Schleife ja afaik auf dem Iterator aufbaut, frage ich mich, ob es da nicht doch eine direkte, elegante Möglichkeit gibt ?!?
Es gibt schon Mögichkeiten, sind aber eigentlich alles nur hacks. Spontan fällt mir sowas ein:
public class RemovableIterable<E> implements Collection<E> {

private Collection<E> collection;

private Iterator<E> iterator;

public RemovableIterable(Collection<E> collection) {
this.collection = collection;
}

public Iterator<E> iterator() {
this.iterator = this.collection.iterator();
return this.iterator;
}

public boolean add(E e) {
throw new UnsupportedOperationException();
}

public boolean addAll(Collection<? extends E> c) {
throw new UnsupportedOperationException();
}

public void clear() {
throw new UnsupportedOperationException();
}

public boolean contains(Object o) {
throw new UnsupportedOperationException();
}

public boolean containsAll(Collection<?> c) {
throw new UnsupportedOperationException();
}

public boolean equals(Object o) {
throw new UnsupportedOperationException();
}

public boolean isEmpty() {
throw new UnsupportedOperationException();
}

public boolean remove(Object o) {
this.iterator.remove();
return true;
}

public boolean removeAll(Collection<?> c) {
throw new UnsupportedOperationException();
}

public boolean retainAll(Collection<?> c) {
throw new UnsupportedOperationException();
}

public int size() {
throw new UnsupportedOperationException();
}

public Object[] toArray() {
throw new UnsupportedOperationException();
}

public <T> T[] toArray(T[] a) {
throw new UnsupportedOperationException();
}

public static void main(String[] args) {
Collection<String> strings = new ArrayList<String>(Arrays.asList(
"In A.D. 2101, war was beginning.",
"What happen?",
"Somebody set up us the bomb.",
"We get signal.",
"What!",
"Main screen turn on.",
"It\'s you!!",
"How are you gentlemen!!",
"All your base are belong to us.",
"You are on the way to destruction.",
"What you say !!",
"You have no chance to survive make your time.",
"Ha Ha Ha Ha ....",
"Captain!! *",
"Take off every \'Zig\'!!",
"You know what you doing.",
"Move \'Zig\'.",
"For great justice."));

RemovableIterable<String> removable = new RemovableIterable<String>(strings);
for (String each : removable) {
if (each.length() > 12) {
removable.remove(each);
}
}

for (String each : removable) {
System.out.println(each);
}

}
}

enhanced-for ist aber wirlich sehr limitiert. So Sachen wie z.B gleichzeitig Index und Element zurückgeben gehen nur über Umwege. Eigentlich ist das ganze selbst ja bloss ein compiler-hack.

Monger
2005-11-03, 08:32:36
Geht also nicht? Schade :(


Was wenn ich sowas mache:



for(Ding ding : liste){
if(!ding.isValid()){
liste.iterator().remove();
}
}


Oder erzeugt er mir damit einen ganz anderen Iterator?

Wie funktioniert das mit dem Iterator überhaupt? Wird bei einem Schleifenbeginn immer ein neuer Iterator erzeugt, der halt bei 0 anfängt, dann solange #next() aufgerufen bis #hasNext() fehlschlägt? Und danach fliegt der Iterator aus dem Scope raus?

HellHorse
2005-11-03, 09:41:51
Geht also nicht? Schade :(
Geht schon, halt einfach über hacks und braucht eine Zeile mehr, siehe oben.

Was wenn ich sowas mache:



for(Ding ding : liste){
if(!ding.isValid()){
liste.iterator().remove();
}
}


Oder erzeugt er mir damit einen ganz anderen Iterator?
Ja, ausser du wrappst die Liste ähnlich wie oben.

Wie funktioniert das mit dem Iterator überhaupt? Wird bei einem Schleifenbeginn immer ein neuer Iterator erzeugt, der halt bei 0 anfängt, dann solange #next() aufgerufen bis #hasNext() fehlschlägt? Und danach fliegt der Iterator aus dem Scope raus?
Genau.

Iterator<Ding> tempN+1 = liste.iterator();
while(tempN+1.hasNext()){
Ding ding = tempN+1.next();
//... dein Code hier
}

Ist wirklich bloss ein besseres Compilermakro. Im Zeifelsfall kannst du ja den erzeugten Bytecode anschauen.