2010-02-26 13 views
14

questo è un modo valido per trovare e rimuovere elemento da una LinkedList in Java utilizzando una per ogni ciclo, è possibile che incoerenza può sorgere:LinkedList: rimuovere un oggetto

for(ObjectType ob : obList) { 
    if(ob.getId() == id) { 
    obList.remove(ob); 
    break; 
    } 
} 

risposta

16

Altri hanno menzionato il punto valido che normalmente questo non è il modo per remove un oggetto da un collezione. TUTTAVIA, in questo caso va bene dal momento che si è break fuori dal ciclo una volta che si remove.

Se si desidera continuare a ripetere dopo un remove, è necessario utilizzare un iteratore. Altrimenti riceverai un ConcurrentModificationException o, nel caso più generale, un comportamento non definito.

Quindi sì, se break fuori dal foreach dopo aver remove, andrà tutto bene.


A chi sta dicendo che questo non riuscirà perché non è possibile modificare una raccolta in un foreach - questo è vero solo se si desidera mantenere l'iterazione. Non è questo il caso, quindi questa scorciatoia va bene.

A ConcurrentModificationException viene controllato e generato dall'iteratore. Qui, dopo lo remove (che si qualifica come modifica simultanea), si esce dal ciclo break. L'iteratore non ha nemmeno la possibilità di rilevarlo.

Può essere meglio se si aggiunge un commento sul break, perché è assolutamente necessario, ecc, perché se questo codice viene poi modificato a continuare l'iterazione dopo un remove, fallirà.

vorrei trattare questo idioma simile a goto (o meglio, l'etichetta break/continue): può sembrare sbagliato in un primo momento, ma quando viene utilizzato con saggezza, si fa per un codice più pulito.

+0

Non ho controllato le specifiche della lingua, ma la sintassi di foreach non è solo la magia del compilatore che usa un iteratore dietro le quinte? – Powerlord

+0

Sì, utilizza un iteratore dietro la scena e quell'iteratore è nascosto da te. – polygenelubricants

+3

Ma è molto probabile che fallisca nella versione futura, quando lo sviluppatore nexte cerca di aggiungere alcune funzionalità senza "vedere" quel trabocchetto. Dovresti documentarlo chiaramente allora. – whiskeysierra

4

Edit: In effetti, lo farà non fallire grazie alla pausa. Vedi la risposta di polygenelubrificante per i dettagli.

Tuttavia, questo è un modo pericoloso di fare. Per eseguire simultaneamente l'iterazione e la modifica di una raccolta in Java, è necessario utilizzare l'oggetto "ListIterator" e utilizzare i metodi "add()" e "remove()" dell'iteratore e non utilizzare quelli della raccolta.

È possibile controllare il doc java per il "java.util.Iterator" e classi "java.util.ListIterator"

+0

"Iterator" non ha un metodo "add()". Solo ListIterator ha. Dovresti farlo notare. – whiskeysierra

+1

@Zorglub, presta attenzione alla 'pausa'. Questo codice non fallirà. – polygenelubricants

+3

Potrebbe non "fallire", ma il codice è, in qualsiasi senso normale, sbagliato. –

1

provare qualcosa di simile:

Iterator<ObjectType> iter = obList.iterator(); 
while (iter.hasNext()) { 
    ObjectType ob = iter.next(); 
    if(ob.getId() == id) { 
    iter.remove(); 
    break; 
    } 
} 

Questo è uno degli ultimi luoghi in cui un Iterator non può essere sostituito da un ciclo foreach.

+0

Non è necessario essere così dettagliato se si interrompe dopo un 'remove'. – polygenelubricants

+0

@polygenelubrificanti: oh, hai ragione. Non mi stavo rendendo conto che l'eccezione si sarebbe verificata solo nel ciclo successivo. – Stroboskop

+2

Se si tratta di un elenco collegato, Iterator.remove() è molto più efficiente di List.remove (Object), poiché quest'ultimo deve cercare nuovamente l'oggetto. Vorrei usare l'Iterator e rimuoverlo per principio. –

0

A CopyOnWriteArrayList potrebbe essere quello che stai cercando. Quando vengono eseguite operazioni mutative, viene creata una copia dell'array sottostante. Ciò consente la modifica degli elementi dell'elenco all'interno di un ciclo for-each. Ricorda però che questa non è una lista concatenata e può essere piuttosto inefficiente.

import java.util.List; 
import java.util.concurrent.CopyOnWriteArrayList; 

public class Main { 

    public static void main(String[] args) { 
     List<String> myList = new CopyOnWriteArrayList<String>(); 

     myList.add("a"); 
     myList.add("b"); 
     myList.add("c"); 

     // Will print [a, b, c] 
     System.out.println(myList); 

     for (String element : myList) { 
      if (element.equals("a")) { 
       myList.remove(element); 
      } 
     } 

     // Will print [b, c] 
     System.out.println(myList); 
    } 

} 
6

Si dovrebbe usare iterator.remove():

Rimuove dalla collezione sottostante l'ultimo elemento restituito dal iteratore (operazione facoltativa). Questo metodo può essere chiamato una sola volta per chiamata alla successiva. Il comportamento di un iteratore è specificato se la raccolta sottostante viene modificato mentre l'iterazione è in corso in alcun modo diverso richiamando questo metodo .

+0

"mentre l'iterazione è in corso" - questa è l'importante distinzione qui. Nel suo caso, una volta modificata la collezione, abortisce l'iterazione. Ecco perché stiamo avendo questa discussione. Nel caso generale, però, sei assolutamente corretto. – polygenelubricants

1

Per evitare un ConcurrentModifiationException , si potrebbe fare:

final Iterator<ObjectType> i = obList.iterator(); 
while (i.hasNext()) { 
    if (i.next().getId() == id) { 
     i.remove(); 
    } 
} 

o

for (int i = 0; i < obList.size(); i++) { 
    if (obList[i].getId() == id) { 
     obList.remove(i); 
    } 
} 

io preferirei la prima. Gestire gli indici è più errorprone e l'iteratore può essere implementato in modo efficiente. E il primo suggerimento funziona con Iterable mentre il secondo richiede una lista.

+0

Basta usare l'iteratore in un ciclo for. –

+0

Questo lascerebbe l'ultima parte del ciclo vuota. Non mi piace. Un po 'si adatta perfettamente lì. – whiskeysierra

+0

Questo codice può potenzialmente effettuare più traslochi. È un comportamento diverso rispetto al codice originale, che rimuove al massimo un elemento. – polygenelubricants

7

È preferibile utilizzare un iteratore e utilizzare il suo metodo di rimozione durante la ricerca di un oggetto iterando su una raccolta per rimuoverlo. Questo perché

  1. La collezione potrebbe essere, ad esempio, una lista concatenata (e nel tuo caso è), il cui metodo di rimozione significa cercare l'oggetto tutto da capo, che di ricerca potrebbe avere O (n) la complessità.
  2. Non è possibile continuare l'iterazione dopo la rimozione a meno che non si utilizzi il metodo di rimozione dell'iteratore. In questo momento stai rimuovendo la prima occorrenza - in futuro potresti dover rimuovere tutte le occorrenze corrispondenti, nel qual caso dovrai riscrivere il ciclo.

vi consiglio, per principio, rinunciando la maggiore e usando qualcosa come questo, invece:

for(Iterator<ObjectType> it=obList.iterator(); it.hasNext();) { 
    if(it.next().getId()==id) { 
     it.remove(); 
     break; 
     } 
    } 

In questo modo non si stanno facendo ipotesi circa la lista sottostante che potrebbe cambiare in futuro.


Confrontare il codice per rimuovere l'ultima voce chiamato dal iteratore remove (formattazione di Sun):

private E remove(Entry<E> e) { 
    if (e == header) 
     throw new NoSuchElementException(); 

    E result = e.element; 
    e.previous.next = e.next; 
    e.next.previous = e.previous; 
    e.next = e.previous = null; 
    e.element = null; 
    size--; 
    modCount++; 
    return result; 
} 

contro ciò rimuovere (Object) deve fare:

public boolean remove(Object o) { 
    if (o==null) { 
     for (Entry<E> e = header.next; e != header; e = e.next) { 
      if (e.element==null) { 
       remove(e); 
       return true; 
      } 
     } 
    } else { 
     for (Entry<E> e = header.next; e != header; e = e.next) { 
      if (o.equals(e.element)) { 
       remove(e); 
       return true; 
      } 
     } 
    } 
    return false; 
} 
+0

Non hai risposto a quello che ho chiesto, ma è una buona intuizione. – Xolve

0

È possibile che questo secondo ciclo dovrebbe essere cambiato un po '

for (int i = 0; i < obList.size();) { 
    if (obList.get(i).getId() == id) { 
     obList.remove(i); 
     continue 
    } 
    ++i; 
} 

o

for (int i = obList.size() - 1; i >= 0; --i) { 
    if (obList.get(i).getId() == id) { 
     obList.remove(i); 
    } 
} 
Problemi correlati