2016-02-28 23 views
20

Questo codice:ArrayList.remove dà risultati diversi quando viene chiamato come Collection.remove

Collection<String> col = new ArrayList<String>();  
    col.add("a"); 
    col.add("b"); 
    col.add("c"); 
    for(String s: col){  
     if(s.equals("b")) 
      col.remove(1); 
     System.out.print(s); 

    } 

stampe: abc

Nel frattempo questo:

ArrayList<String> col = new ArrayList<String>();  
    col.add("a"); 
    col.add("b"); 
    col.add("c"); 
    for(String s: col){  
     if(s.equals("b")) 
      col.remove(1); 
     System.out.print(s); 

    } 

stampe: ab

Tuttavia dovrebbe stampare lo stesso risultato ... Qual è il problema?

risposta

30

Collection ha solo il metodo boolean remove(Object o), che rimuove l'oggetto passato se trovato.

ArrayList ha anche public E remove(int index), che può rimuovere un elemento dal suo indice.

Il tuo primo snippet chiama boolean remove(Object o), che non rimuove nulla, dal momento che il tuo ArrayList non contiene 1. Il secondo snippet chiama public E remove(int index) e rimuove l'elemento il cui indice era 1 (ovvero rimuove "b").

Il diverso comportamento deriva dal fatto che la risoluzione del sovraccarico del metodo si verifica in fase di compilazione e dipende dal tipo di tempo di compilazione della variabile per la quale si sta chiamando il metodo. Quando il tipo di col è Collection, solo i metodi remove dell'interfaccia Collection (e i metodi ereditati da tale interfaccia) vengono considerati per la risoluzione di sovraccarico.

Se si sostituisce col.remove(1) con col.remove("b"), entrambi gli snippet si comporterebbero allo stesso modo.

Come Tamoghna Chowdhury ha commentato, boolean remove(Object o) può accettare un primitivo argomento - int nel tuo caso - a causa di auto-boxing del int a un Integer istanza. Per il secondo snippet, il motivo public E remove(int index) viene scelto su boolean remove(Object o) è che il processo di risoluzione dell'overload del metodo tenta innanzitutto di trovare un metodo di abbinamento senza eseguire conversioni auto-box/unboxing, quindi considera solo public E remove(int index).

+0

si può aggiungere la parte su come la 'Collezioni Il metodo .remove() 'accetta un' int' dovuto al boxing automatico? –

+0

@TamoghnaChowdhury sure – Eran

+0

Un'altra cosa da aggiungere ArrayList è la raccolta fail-fast, quindi se si intende rimuovere qualsiasi elemento dall'elenco durante l'attraversamento di usare CopyOnWriteArrayList, dato che è fail-safe – emkays

9

Per rimuovere in modo sicuro da un Collection durante l'iterazione su di esso, è necessario utilizzare un Iterator.

ArrayList<String> col = new ArrayList<String>();  
col.add("a"); 
col.add("b"); 
col.add("c"); 

Iterator<String> i = col.iterator(); 
while (i.hasNext()) { 
    String s = i.next(); // must be called before you can call remove 
    if(s.equals("b")) 
     i.remove(); 
    System.out.print(s); 
} 

Per quanto riguarda il motivo per cui la rimozione dalla raccolta non funziona per voi, mentre il ArrayList lavorato è per i seguenti motivi:

  1. Il metodo java.util.ArrayList.remove(int index) rimuove l'elemento nella posizione specificata in questo elenco . Sposta tutti gli elementi successivi a sinistra (sottrae uno dai loro indici). Quindi, questo ha funzionato per te.

  2. Il metodo java.util.Collection.remove(Object o) rimuove una singola istanza dell'elemento specificato da questa raccolta, se è presente (è un'operazione facoltativa). Più formalmente, rimuove un elemento e tale che (o==null ? e==null : o.equals(e)), se questa raccolta contiene uno o più di tali elementi.Restituisce true se questa raccolta contiene l'elemento specificato (o in modo equivalente, se questa raccolta è cambiata in seguito alla chiamata).

Spero che questo aiuti.

2

Entrambi i frammenti sono suddivisi in diversi modi!

Caso 1 (con Collection<String> col):

Da un Collection è indicizzata, l'unico metodo remove sua interfaccia espone è Collection.remove(Object o), che rimuove l'oggetto uguale specificato. Fare col.remove(1); prima chiama Integer.valueOf(1) per ottenere un oggetto Integer, quindi chiede l'elenco per rimuovere quell'oggetto. Poiché l'elenco non contiene alcun oggetto Integer, non viene rimosso nulla. L'iterazione continua normalmente attraverso l'elenco e viene stampato abc.

Caso 2 (con ArrayList<String> col):

Quando col tipo 's in fase di compilazione è ArrayList, chiamando col.remove(1); invece richiama il metodo ArrayList.remove(int index) di rimuovere l'elemento nella posizione specificata, rimuovendo così b.

Ora, perché non viene stampato c? Per eseguire il loop su una raccolta con la sintassi for (X : Y), dietro le quinte chiama la raccolta per ottenere un oggetto Iterator. Per il Iterator restituito da un ArrayList (e la maggior parte delle raccolte) non è sicuro eseguire modifiche strutturali all'elenco durante l'iterazione - a meno che non lo si modifichi con i metodi dello Iterator stesso - perché lo Iterator si confonderà e perderà traccia di quale elemento ritornare dopo. Ciò può comportare la ripetizione di elementi ripetuti, elementi saltati o altri errori. Ecco cosa succede qui: l'elemento c è presente nell'elenco ma non è mai stato stampato perché hai confuso lo Iterator.

Quando un Iterator è in grado di rilevare questo problema è accaduto che ti avviserà lanciando un ConcurrentModificationException. Tuttavia, il controllo che un Iterator esegue per il problema è ottimizzato per la velocità, non è corretto al 100% e non sempre rileva il problema. Nel codice, se si modifica s.equals("b") a s.equals("a") o s.equals("c"), viene generata un'eccezione (sebbene ciò potrebbe dipendere dalla particolare versione di Java). Dalle ArrayList documentation:

Gli iteratori restituiti da iterator e listIterator i metodi di questa classe sono fail-fast: se la lista è strutturalmente modificato in qualsiasi momento dopo la creazione del iteratore, in alcun modo se non attraverso l'iteratore proprio Metodi remove o add, l'iteratore genererà un valore ConcurrentModificationException. Quindi, di fronte a modifiche simultanee, l'iteratore fallisce rapidamente e in modo pulito, piuttosto che rischiare di comportarsi in modo arbitrario e non deterministico in un tempo indeterminato nel futuro.

Si noti che il comportamento fail-fast di un iteratore non può essere garantito in quanto è, in generale, impossibile fornire garanzie rigide in presenza di modifiche simultanee non sincronizzate. Gli iteratori fail-fast generano ConcurrentModificationException in base al miglior sforzo.


per rimuovere gli elementi durante l'iterazione, è necessario modificare il for (X : Y) in stile del ciclo in un ciclo manuale su un esplicito Iterator, utilizzando il suo metodo remove:

for (Iterator<String> it = col.iterator(); it.hasNext();) { 
    String s = it.next(); 
    if (s.equals("b")) 
     it.remove(); 
    System.out.print(s); 
} 

Questo è ora completamente sicuro . Ripeterà tutti gli elementi esattamente una volta (stampa abc), mentre l'elemento b verrà rimosso.

Se si desidera, è possibile ottenere lo stesso effetto senza un Iterator utilizzare un int i ciclo in stile, se si regola con attenzione l'indice dopo eventuali rimozioni:

for (int i = 0; i < col.size(); i++) { 
    String s = col.get(i); 
    if (s.equals("b")) { 
     col.remove(i); 
     i--; 
    } 
    System.out.print(s); 
} 
+0

Sì, questo. Inoltre, 'col.removeIf (s -> s.equals (" b "))'. –

Problemi correlati