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
Однако он должен печатать тот же результат...
В чем проблема?
Ответы
Ответ 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);
}