Подтвердить что ты не робот

ArrayList.remove дает другой результат при вызове 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);  

    } 

Отпечатки: abc

Между тем этот:

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

    } 

Отпечатки: ab

Однако он должен печатать тот же результат... В чем проблема?

4b9b3361

Ответ 1

Collection имеет только метод boolean remove(Object o), который удаляет переданный объект, если найден.

ArrayList также имеет public E remove(int index), который может удалить элемент по его индексу.

Ваш первый фрагмент вызывает boolean remove(Object o), который ничего не удаляет, так как ваш ArrayList не содержит 1. Второй фрагмент вызывает public E remove(int index) и удаляет элемент с индексом 1 (т.е. Удаляет "b").

Различное поведение возникает из-за того, что во время компиляции возникает ошибка перегрузки метода и зависит от типа времени компиляции переменной, для которой вы вызываете метод. Если тип col равен Collection, для перегрузки разрешения рассматриваются только методы remove интерфейса Collection (и методы, наследуемые этим интерфейсом).

Если вы замените col.remove(1) на col.remove("b"), оба фрагмента будут вести себя одинаково.

Как заметил Тамохна Чоудхури, boolean remove(Object o) может принять примитивный аргумент - int в вашем случае - из-за автоматического бокса в int экземпляре Integer. Для второго фрагмента причина public E remove(int index) выбрана над boolean remove(Object o) заключается в том, что метод перегрузки разрешения сначала пытается найти метод сопоставления без преобразования auto-boxing/unboxing, поэтому он учитывает только public E remove(int index).

Ответ 2

Чтобы безопасно удалить из Collection во время итерации по нему, вы должны использовать 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);
}

В связи с тем, что удаление из коллекции не работает для вас, а ArrayList работает из-за следующего:

  • Метод java.util.ArrayList.remove(int index) удаляет элемент в указанной позиции в этом списке. Сдвигает любые последующие элементы слева (вычитает один из их индексов). Следовательно, этот работал для вас.

  • Метод java.util.Collection.remove(Object o) удаляет один экземпляр указанного элемента из этой коллекции, если он присутствует (это необязательная операция). Более формально удаляет элемент e такой, что (o==null ? e==null : o.equals(e)), если эта коллекция содержит один или несколько таких элементов. Возвращает true, если в этом сборнике содержится указанный элемент (или, что то же самое, если этот набор изменился в результате вызова).

Надеюсь, это поможет.

Ответ 3

Оба фрагмента разбиты по-разному!

Случай 1 (с Collection<String> col):

Так как a Collection неиндексирован, единственным способом remove, который предоставляет его интерфейс, является Collection.remove(Object o), который удаляет указанный равный объект, Выполняя col.remove(1);, сначала вызовите Integer.valueOf(1), чтобы получить объект Integer, а затем попросит список удалить этот объект. Поскольку список не содержит таких объектов Integer, ничего не удаляется. Итерация продолжается нормально через список и abc распечатывается.

Случай 2 (с ArrayList<String> col):

Когда col тип времени компиляции ArrayList, вызов col.remove(1); вместо этого вызывает метод ArrayList.remove(int index), чтобы удалить элемент в указанной позиции, удаляя b.

Теперь, почему не печатается c? Чтобы перебрать коллекцию с синтаксисом for (X : Y), она за кулисами вызывает сбор, чтобы получить объект Iterator. Для Iterator, возвращаемого ArrayList (и большинства коллекций) , небезопасно выполнять структурные изменения в списке во время итерации - если вы не измените его с помощью методов самого Iterator - потому что Iterator запутается и потеряет следы того, какой элемент будет возвращаться дальше. Это может привести к тому, что элементы повторяются несколько раз, пропущенные элементы или другие ошибки. Что здесь происходит: элемент c присутствует в списке, но никогда не распечатывается, потому что вы путаете Iterator.

Когда Iterator может обнаружить, что эта проблема произошла, она предупредит вас, выбросив ConcurrentModificationException. Однако проверка того, что Iterator для задачи оптимизирована для скорости, а не на 100% правильности, и не всегда обнаруживает проблему. В коде, если вы меняете s.equals("b") на s.equals("a") или s.equals("c"), он генерирует исключение (хотя это может зависеть от конкретной версии Java). Из ArrayList документации:

Итераторы, возвращаемые с помощью методов этого класса Iterator и listIterator, работают с ошибкой: если список структурно изменен в любое время после создания итератора, любым способом, кроме как через собственный итератор remove или add, итератор будет бросать ConcurrentModificationException. Таким образом, перед лицом одновременной модификации итератор быстро и чисто, а не рискует произвольным, недетерминированным поведением в неопределенное время в будущем.

Обратите внимание, что отказоустойчивое поведение итератора не может быть гарантировано, поскольку, вообще говоря, невозможно сделать какие-либо серьезные гарантии при наличии несинхронизированной параллельной модификации. Неудачные итераторы бросают ConcurrentModificationException в наилучшей форме.


Чтобы удалить элементы во время итерации, вы должны изменить стиль цикла for (X : Y) в ручной цикл над явным Iterator, используя его метод remove:

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

Теперь это абсолютно безопасно. Он будет перебирать все элементы ровно один раз (печать abc), а элемент b будет удален.

Если вы хотите, вы можете добиться такого же эффекта без Iterator с использованием цикла int i -style, если вы тщательно настроите индекс после удаления:

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