Ответ 1
Позвольте мне привести несколько примеров с некоторыми альтернативами, чтобы избежать ConcurrentModificationException
.
Предположим, что у нас есть следующий сборник книг
List<Book> books = new ArrayList<Book>();
books.add(new Book(new ISBN("0-201-63361-2")));
books.add(new Book(new ISBN("0-201-63361-3")));
books.add(new Book(new ISBN("0-201-63361-4")));
Сбор и удаление
Соберите все записи, которые вы хотите удалить, в цикле расширенного цикла, и после завершения итерации вы удалите все найденные записи.
ISBN isbn = new ISBN("0-201-63361-2");
List<Book> found = new ArrayList<Book>();
for(Book book : books){
if(book.getIsbn().equals(isbn)){
found.add(book);
}
}
books.removeAll(found);
Предположим, что операция, которую вы хотите сделать, это "удалить".
Если вы хотите "добавить", этот подход также будет работать, но я бы предположил, что вы будете перебирать другую коллекцию, чтобы определить, какие элементы вы хотите добавить ко второй коллекции, а затем выпустить метод addAll
в конце.
Использование ListIterator
Или вы можете использовать ListIterator
, который поддерживает метод remove/add во время самой итерации.
ListIterator<Book> iter = books.listIterator();
while(iter.hasNext()){
if(iter.next().getIsbn().equals(isbn)){
iter.remove();
}
}
Опять же, я использовал метод "remove", который, как кажется, подразумевает ваш вопрос, но вы также можете использовать его метод add
для добавления новых элементов во время итерации.
Использовать потоки JDK 8
Или используя JDK 8 потоков, lambdas/closures:
ISBN other = new ISBN("0-201-63361-2");
List<Book> filtered = books.stream()
.filter(b -> b.getIsbn().equals(other))
.collect(Collectors.toList());
В этих двух последних случаях для фильтрации элементов из коллекции и переназначения исходной ссылки на отфильтрованную коллекцию (т.е. books = filtered
) или использовали отфильтрованную коллекцию в removeAll
найденные элементы из исходной коллекции (т.е. books.removeAll(filtered)
).
Использовать подсчет или подмножество
Существуют и другие альтернативы. Если список отсортирован и вы хотите удалить последовательные элементы, вы можете создать подсписку, а затем очистить его:
books.subList(0,5).clear();
Так как подвыбор поддерживается исходным списком, это будет эффективным способом удаления этого подмножества элементов.
Нечто подобное может быть достигнуто с помощью отсортированных наборов с использованием метода NavigableSet.subSet
или любого из предложенных там методов разрезания.
Вопросы:
Какой метод вы используете, может зависеть от того, что вы собираетесь делать
- Метод сбора и удаления работает с любой коллекцией (Collection, List, Set и т.д.).
- Метод ListIterator работает только со списками при условии, что их реализация
ListIterator
предлагает поддержку для операций добавления и удаления. - Подход
Iterator
вообще будет работать над любой коллекцией, если вы только собираетесь использовать метод удаления итератора. - В подходе ListIterator/Iterator очевидное преимущество - не копировать ничего.
- Примеры сторонних и JDK-8 потоков фактически ничего не удаляют, но ищут нужные элементы, тогда вы можете заменить исходную ссылку на новую, и пусть старый будет собран мусором.
- В процессе сбора и удаления недостатком является то, что мы должны повторять итерацию дважды. Мы перебираем петлю в поисках элемента, и как только мы его найдем, мы попросим удалить его из исходного списка, что означало бы вторую итерационную работу для поиска данного элемента.
- Я думаю, что стоит упомянуть, что метод remove интерфейса
Iterator
помечается как необязательный в Javadocs, что означает, что могут существовать реализации Iterator, которые могут бросатьUnsupportedOperationException
. Таким образом, я бы сказал, что этот подход менее безопасен, чем первый.