2016-02-28 7 views
20

Dieser Code genannt:ArrayList.remove gibt anderes Ergebnis, wenn sie als 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); 

    } 

druckt: abc

Inzwischen dieses:

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); 

    } 

druckt: ab

Es sollte jedoch das gleiche Ergebnis drucken ... Was ist das Problem?

Antwort

30

Collection hat nur boolean remove(Object o) Methode, die das übergebene Objekt entfernt, wenn gefunden.

ArrayList hat auch public E remove(int index), die ein Element durch seinen Index entfernen können.

Ihr erstes Snippet ruft boolean remove(Object o) auf, das nichts entfernt, da Ihr ArrayList1 nicht enthält. Ihr zweites Snippet ruft public E remove(int index) auf und entfernt das Element, dessen Index 1 war (d. H. Es entfernt "b").

Das unterschiedliche Verhalten ergibt sich aus der Tatsache, dass die Methodenüberladungsauflösung zur Kompilierzeit auftritt und vom Kompilierzeittyp der Variablen abhängt, für die Sie die Methode aufrufen. Wenn der Typ colCollection ist, werden nur remove Methoden der Schnittstelle Collection (und Methoden, die von dieser Schnittstelle übernommen werden) für die Überlastung der Auflösung berücksichtigt.

Wenn Sie col.remove(1) durch col.remove("b") ersetzen, würden sich beide Snippets gleich verhalten.

Als Tamoghna Chowdhury kommentierte boolean remove(Object o) kann ein primitives Argument akzeptieren - int in Ihrem Fall - wegen Auto-Box der int zu einer Integer Instanz. Für das zweite Snippet wird der Grund public E remove(int index) über boolean remove(Object o) gewählt, dass der Überladungsauflösungsprozess der Methode zuerst versucht, eine übereinstimmende Methode zu finden, ohne Auto-Boxing/Unboxing-Konvertierungen durchzuführen, so dass nur public E remove(int index) berücksichtigt wird.

+0

Können Sie den Teil darüber, wie die 'Sammlungen hinzufügen .remove() 'Methode akzeptiert ein" int "wegen Autoboxing? –

+0

@TamoghnaChowdhury sure – Eran

+0

Eine weitere Sache ArrayList hinzufügen ist Fail-schnelle Sammlung, also wenn Sie ein Element aus der Liste während des Traversieren entfernen, dann verwenden Sie CopyOnWriteArrayList, da es fehlersicher ist – emkays

9

Um eine Collection sicher zu entfernen, während Sie darüber iterieren, sollten Sie eine Iterator verwenden.

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); 
} 

Bezüglich des Grundes, warum die Entfernung von der Sammlung für Sie nicht funktioniert, während die ArrayList ist gearbeitet aus folgendem Grunde:

  1. Das java.util.ArrayList.remove(int index) Verfahren entfernt das Element an der angegebenen Position in dieser Liste . Verschiebt alle nachfolgenden Elemente nach links (subtrahiert eins von ihren Indizes). Daher hat dieser für dich gearbeitet.

  2. Die Methode java.util.Collection.remove(Object o) entfernt eine einzelne Instanz des angegebenen Elements aus dieser Auflistung, wenn sie vorhanden ist (es handelt sich um eine optionale Operation). Formal entfernt ein Element e wieO, wenn diese Sammlung ein oder mehrere solcher Elemente enthält.Gibt true zurück, wenn diese Auflistung das angegebene Element enthielt (oder äquivalent, wenn diese Sammlung als Ergebnis des Aufrufs geändert wurde).

Hoffe, das hilft.

2

Beide Snippets sind auf verschiedene Arten unterbrochen!

Fall 1 (mit Collection<String> col):

Da ein Collection unindexed ist, ist die einzige Methode remove seine Schnittstelle aussetzt Collection.remove(Object o), die das angegebene Objekt gleich entfernt. Doing col.remove(1); zuerst ruft Integer.valueOf(1), um ein Integer Objekt zu erhalten, dann fragt die Liste, um dieses Objekt zu entfernen. Da die Liste keine solchen Integer Objekte enthält, wird nichts entfernt. Die Iteration wird normalerweise über die Liste fortgesetzt und abc ausgedruckt.

Fall 2 (mit ArrayList<String> col):

Wenn col ‚s kompiliert Zeittyp ArrayList wird, ruft col.remove(1); Aufruf statt das Verfahren ArrayList.remove(int index) das Element an der angegebenen Position zu entfernen, wodurch b entfernen.

Nun, warum ist nicht c ausgedruckt? Um eine Sammlung mit der for (X : Y)-Syntax zu durchlaufen, ruft sie hinter den Kulissen die Sammlung auf, um ein Objekt Iterator zu erhalten. Für die Iterator zurückgegeben von ArrayList (und die meisten Sammlungen) ist es nicht sicher, strukturelle Änderungen an der Liste während der Iteration durchzuführen - es sei denn, Sie ändern es durch die Methoden der Iterator selbst - weil die Iterator wird verwirrt und den Überblick verlieren welches Element als nächstes zurückkommt. Dies kann dazu führen, dass Elemente mehrmals durchlaufen werden, Elemente übersprungen werden oder andere Fehler auftreten. Das passiert hier: Element c ist in der Liste vorhanden, aber nie ausgedruckt, weil Sie die Iterator verwechselt haben.

Wenn ein Iterator erkennt, dass dieses Problem aufgetreten ist, wird es Sie warnen, indem Sie eine werfen. Die Überprüfung, die eine Iterator für das Problem durchführt, ist jedoch auf Geschwindigkeit und nicht auf 100% Korrektheit optimiert, und das Problem wird nicht immer erkannt. Wenn Sie in Ihrem Code s.equals("b") in s.equals("a") oder s.equals("c") ändern, wird die Ausnahme ausgelöst (obwohl dies von der jeweiligen Java-Version abhängig sein kann). Aus den ArrayList documentation:

Die die zurück Iteratoren von dieser Klasse iterator und listIterator Methoden sind ausfall schnell: wenn die Liste strukturell jederzeit geändert, nachdem der Iterator erstellt wird, in irgendeiner Weise, außer durch den Iterator eigenen remove oder add Methoden, der Iterator wird ConcurrentModificationException werfen. Angesichts der gleichzeitigen Modifikation versagt der Iterator daher schnell und sauber, anstatt willkürliches, nicht-deterministisches Verhalten zu einem unbestimmten Zeitpunkt in der Zukunft zu riskieren.

Beachten Sie, dass das Fail-Fast-Verhalten eines Iterators nicht garantiert werden kann, da es im Allgemeinen unmöglich ist, bei unsynchronisierten simultanen Modifikationen harte Garantien zu geben. Fail-schnelle Iteratoren werfen ConcurrentModificationException auf Best-Effort-Basis.


Um Elemente während der Iteration zu entfernen, können Sie die for (X : Y) -Stil der Schleife über eine explizite Iterator in einen manuellen Schleife ändern müssen, seine remove Methode:

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

Dies ist jetzt völlig sicher . Es wird alle Elemente genau einmal durchlaufen (Drucken abc), während das Element b entfernt wird.

Wenn Sie möchten, können Sie den gleichen Effekt ohne Iterator erreichen eine int i -Stil-Schleife, wenn Sie sorgfältig den Index nach jedem Umzug einstellen:

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

Ja, das. Auch "col.removeIf (s -> s.gleiche (" b "))". –

Verwandte Themen