2009-03-09 14 views
17

Questa domanda è un caso più particolare del problema descritto (e risolto) in this question.Java: come rimuovere elementi da un elenco mentre si itera/aggiungendo ad esso

Ho due metodi, stopAndRemove (ServerObject server) e un metodo close(). L'ultimo dovrebbe chiudere tutti i server e rimuoverli dall'elenco dei server. La lista è definita come

List<ServerObject> server. 

Io non voglio avere quasi lo stesso codice stopAndRemove in closeCurrentlyOpen, quindi voglio fare qualcosa di simile:

public void closeCurrentlyOpen() { 
    for(ServerObject server : this.servers) { 
     stopAndRemove(server) 
    } 
} 

Questo non funziona, in quanto questo causerà una ConcurrentModificationException. Ho provato a fare una copia della lista

List<ServerObject> copyList = new ArrayList<ServerObject>(this.servers); 

e usarlo come elenco per il ciclo foreach. Ma allora potrebbe essere possibile che un altro thread accoda un server alla lista dei server mentre sto iterando su copyList ma closeCurrentlyOpen dovrebbe generare un elenco emtpy. Come il metodo addServerToList è sincronizzato con il server-list, facendo questo

public void closeCurrentlyOpen() { 
    synchronized(this.servers) { 
    for(ServerObject server : this.servers) { 
     stopAndRemove(server) 
    } 
    } 
} 

risolverà il problema con le modifiche. Ma allora non posso sincronizzare il codice nel metodo stopAndRemove che è necessario se viene chiamato direttamente.

Mi sembra che il design di questi tre metodi abbia probabilmente bisogno di un workover. Idee qualcuno?

risposta

17

Dividere un metodo stop() da stopAndRemove(). Quindi scrivi il ciclo con un iteratore esplicito, fai lo stop e poi iterator.remove().

"e" in un nome di metodo è un odore di codice.

+0

Ho fatto così ... fermare tutti gli elementi dal ciclo di stop, quindi list.clean() per rimuovere tutto. Sì, "e" è l'odore del codice qui. Ho dato questo nome nell'esempio quindi non ho bisogno di descrivere quello che faccio. – Arvodan

4

Quando l'ho già fatto, ho sempre utilizzato la raccolta LinkedList "old school", un Iterator e il metodo Iterator.remove() per rimuovere l'elemento corrente.

2

Riforma tutti i codice di arresto ServerObject da stopAndRemove in un metodo stopServer privato, quindi esegui la rimozione separatamente in stopAndRemove e closeCurrentlyOpen. Quindi puoi usare un ListIterator per rimuoverli (o semplicemente fermarli tutti in un ciclo for e cancellare l'elenco alla fine).

+0

questo è il modo in cui lo faccio ora. Penso che sia anche quello che ha suggerito Starblue. – Arvodan

0

È necessario ottenere un iteratore e rimuoverlo. Stai ottenendo l'eccezione perché gli iteratori sono fail-fast in java.

+0

Non sono il down-voter ma: non è iteratore che è fail-fast ma l'implementazione dell'elenco fornito nel jdk. – Nicolas

+0

Forse dovrei aver detto che la raccolta java usa gli iteratori veloci falliti. – amit

13

Forse questo è il modo sbagliato per farlo, ma creo sempre una raccolta di rimozione, che contiene indici o riferimenti agli oggetti che devono essere rimossi. Quindi eseguo l'iterazione su quella raccolta e rimuovo quegli indici/oggetti dalla raccolta originale. Probabilmente non il più efficiente ma ha fatto il lavoro.

Invece di

for(Collection things : thing) 
    things.remove(thing) 

Io uso

Collection toRemove = new LinkedList(); 
for(things : thing) 
    toRemove.add(thing); 

for(toRemove : thing) 
    things.remove(thing) 
+0

No, questo è il modo giusto per farlo. Oppure crea una nuova collezione senza gli elementi rimossi e sostituisci la collezione originale con essa. Rimuovere oggetti da una raccolta mentre stai scorrendo è il tipo di merda che dà un brutto codice imperitivo :) – U62

+2

imperativo * Scusa, non posso farci niente :( –

+0

Il problema che vedo qui è: Questa soluzione vuole interrompi qualsiasi thread dall'aggiunta di dati alla lista Nelle altre soluzioni sopra questa operazione viene eseguita tramite blocchi sincronizzati: – Arvodan

1

Rispondendo al titolo della questione, non i dettagli specifici del l'esempio dato. In realtà, questa soluzione non è nemmeno appropriata nella situazione data (il refactoring è appropriato, come suggerito da altri).

Tuttavia, sembra che molti programmatori Java non siano a conoscenza di CopyOnWriteArrayList (parte di JDK dalla versione 1.5) e stiano cercando di implementare le proprie soluzioni per lo stesso problema (elenco di copie prima di iterare).

1

... rimozione dei file che non sono XML da un elenco di directory ...

List<File> files = Arrays.asList(dir.listFiles()); 

Iterator<File> i = files.iterator(); 

while (i.hasNext()) { 
    File file = i.next(); 
    if (!file.getName().endsWith(".xml")) { 
     i.remove(); 
    } 
} 
+0

Basta notare che questo non è un * ideale * modo per farlo. – Spider

+0

Se qualcuno sa come aggiungere un blocco su questo, mi piacerebbe vedere come. Ciò renderebbe quindi questo un esempio solido. – Spider

+0

In JDK7, ho ottenuto * Eccezione nel thread "main" java.lang.UnsupportedOperationException * perché * Arrays.asList * restituisce la dimensione dell'elenco fissa. – vanduc1102

1

Simile a firebird84. Ma puoi usare removeAll (Collection c) api

for(String exitingPermission : existingPermissions){     
    //remove all permissions for the screen and add the new ones 
    if(exitingPermission.split("_")[0].equals(screen)){ 
     removePermissions.add(exitingPermission); 
    } 
} 
existingPermissions.removeAll(removePermissions); 
Problemi correlati