Коллекция бросает или не бросает ConcurrentModificationException на основе содержимого коллекции
Следующий код Java выдает ConcurrentModificationException
, как ожидалось:
public class Evil
{
public static void main(String[] args) {
Collection<String> c = new ArrayList<String>();
c.add("lalala");
c.add("sososo");
c.add("ahaaha");
removeLalala(c);
System.err.println(c);
}
private static void removeLalala(Collection<String> c)
{
for (Iterator<String> i = c.iterator(); i.hasNext();) {
String s = i.next();
if(s.equals("lalala")) {
c.remove(s);
}
}
}
}
Но следующий пример, который отличается только содержимым Collection
, выполняется без каких-либо исключений:
public class Evil {
public static void main(String[] args)
{
Collection<String> c = new ArrayList<String>();
c.add("lalala");
c.add("lalala");
removeLalala(c);
System.err.println(c);
}
private static void removeLalala(Collection<String> c) {
for (Iterator<String> i = c.iterator(); i.hasNext();) {
String s = i.next();
if(s.equals("lalala")) {
c.remove(s);
}
}
}
}
Отпечатает вывод "[lalala]". Почему второй пример не бросает ConcurrentModificationException
, когда делает первый пример?
Ответы
Ответ 1
Короткий ответ
Поскольку отказоустойчивое поведение итератора не гарантируется.
Длинный ответ
Вы получаете это исключение, потому что вы не можете манипулировать коллекцией, итерации по ней, за исключением итератора.
Плохо:
// we're using iterator
for (Iterator<String> i = c.iterator(); i.hasNext();) {
// here, the collection will check it hasn't been modified (in effort to fail fast)
String s = i.next();
if(s.equals("lalala")) {
// s is removed from the collection and the collection will take note it was modified
c.remove(s);
}
}
Хорошо:
// we're using iterator
for (Iterator<String> i = c.iterator(); i.hasNext();) {
// here, the collection will check it hasn't been modified (in effort to fail fast)
String s = i.next();
if(s.equals("lalala")) {
// s is removed from the collection through iterator, so the iterator knows the collection changed and can resume the iteration
i.remove();
}
}
Теперь к "почему": в приведенном выше коде обратите внимание на то, как выполняется проверка модификации - удаление помещает коллекцию как измененную, а следующая итерация проверяет любые изменения и терпит неудачу, если обнаруживает, что коллекция изменена. Еще одна важная вещь: ArrayList
(не уверен в других коллекциях) не проверяет на модификацию в hasNext()
.
Следовательно, могут произойти две странные вещи:
- Если вы удалите последний элемент во время итерации, ничего не будет выброшено
- Это потому, что нет "следующего" элемента, поэтому итерация заканчивается до достижения кода проверки модификации
- Если вы удалите второй-последний элемент,
ArrayList.hasNext()
также вернет false
, потому что итератор current index
теперь указывает на последний элемент (бывший второй по-последнему).
- Так что даже в этом случае после удаления нет элемента "next"
Обратите внимание, что все это соответствует Документация ArrayList:
Обратите внимание, что отказоустойчивое поведение итератора не может быть гарантировано, поскольку, вообще говоря, невозможно сделать какие-либо серьезные гарантии при наличии несинхронизированной параллельной модификации. Неуправляемые итераторы бросают ConcurrentModificationException с максимальной эффективностью. Поэтому было бы неправильно писать программу, зависящую от этого исключения, за ее правильность: неудачное поведение итераторов должно использоваться только для обнаружения ошибок.
Отредактировано для добавления:
Этот вопрос содержит некоторую информацию о том, почему проверка параллельной модификации не выполняется в hasNext()
и выполняется только в next()
.
Ответ 2
Если вы посмотрите на исходный код тетера ArrayList
(частный вложенный класс Itr
), вы увидите недостаток в коде.
Код должен быть неудачным, что делается внутри итератора внутри, вызывая checkForComodification()
, однако hasNext()
не делает этот вызов, вероятно, по соображениям производительности.
Вместо hasNext()
просто:
public boolean hasNext() {
return cursor != size;
}
Это означает, что когда вы находитесь во втором последнем элементе списка, а затем удаляете элемент (любой элемент), размер уменьшается, а hasNext()
думает, что вы находитесь на последнем элементе (который вы weren ' t) и возвращает false
, пропуская итерацию последнего элемента без ошибок.
ОЙ!!!!
Ответ 3
Из других ответов вы знаете, что является правильным способом удаления элемента в коллекции во время итерации коллекции.
Я даю здесь объяснение основному вопросу. И ответ на ваш вопрос лежит в следующей трассировке стека
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
at java.util.ArrayList$Itr.next(Unknown Source)
at com.ii4sm.controller.Evil.removeLalala(Evil.java:23)
at com.ii4sm.controller.Evil.main(Evil.java:17)
В stacktrace очевидно, что строка i.next();
выдает ошибку. Но когда у вас есть только два элемента в коллекции.
Collection<String> c = new ArrayList<String>();
c.add("lalala");
c.add("lalala");
removeLalala(c);
System.err.println(c);
Когда первый удаляется i.hasNext()
возвращает false, а i.next()
никогда не выполняется для исключения исключения
Ответ 4
вы должны удалить из iterator
(i) не collection
(c) напрямую;
попробуйте следующее:
for (Iterator<String> i = c.iterator(); i.hasNext();) {
String s = i.next();
if(s.equals("lalala")) {
i.remove(); //note here, changing c to i with no parameter.
}
}
EDIT:
Причина, по которой первая попытка вызывает исключение, а вторая - не просто из-за количества элементов в вашей коллекции.
поскольку первый из них будет проходить цикл более одного раза, а второй - только один раз. поэтому у него нет возможности выбросить исключение
Ответ 5
Вы не можете удалить из списка, если вы просматриваете его с циклом "для каждого".
Вы не можете удалить элемент из коллекции, которую вы итерируете. Вы можете обойти это, явно используя Итератор и удалив там элемент. Вы можете использовать Итератор.
Если вы используете код ниже, вы не получите никаких исключений:
private static void removeLalala(Collection<String> c)
{
/*for (Iterator<String> i = c.iterator(); i.hasNext();) {
String s = i.next();
if(s.equals("lalala")) {
c.remove(s);
}
}*/
Iterator<String> it = c.iterator();
while (it.hasNext()) {
String st = it.next();
if (st.equals("lalala")) {
it.remove();
}
}
}