Мы все знаем, что вы не можете этого сделать:
for (Object i : l) {
if (condition(i)) {
l.remove(i);
}
}
ConcurrentModificationException и т. д. ... это, по-видимому, работает иногда, но не всегда. Вот какой-то конкретный код:
public static void main(String[] args) {
Collection<Integer> l = new ArrayList<Integer>();
for (int i=0; i < 10; ++i) {
l.add(new Integer(4));
l.add(new Integer(5));
l.add(new Integer(6));
}
for (Integer i : l) {
if (i.intValue() == 5) {
l.remove(i);
}
}
System.out.println(l);
}
Это, конечно же, приводит к:
Exception in thread "main" java.util.ConcurrentModificationException
... хотя несколько потоков не делают этого ... Во всяком случае.
Какое лучшее решение этой проблемы? Как я могу удалить элемент из коллекции в цикле, не выбрасывая это исключение?
Я также использую произвольное Collection здесь, не обязательно ArrayList, поэтому вы не можете полагаться на get.
это может быть не лучшим образом, но для большинства маленьких случаев это должно быть приемлемым:
"создать второй пустой массив и добавить только те, которые вы хотите сохранить"Я не помню, где я читал это ... для справедливости я сделаю эту вики в надежде, что кто-то найдет ее или просто не заработает репутацию, которую я не заслуживаю.
С помощью Java 8 вы можете использовать новый метод removeIf. Применяется к вашему примеру:
Collection<Integer> coll = new ArrayList<Integer>();
//populate
coll.removeIf(i -> i.intValue() == 5);
Collection<Integer> l = new ArrayList<Integer>();//Do the collection thing...
l.removeIf(i -> i == 5); //iterates through the collection and removes every occurence of 5
Лямбда-выражения и методы сбора в Jdk 8 входят в Handy и добавляют некоторый синтаксический сахар
С Eclipse Collections (ранее GS Collections) метод removeIf, определенный в MutableCollection, будет работать:
MutableList<Integer> list = Lists.mutable.of(1, 2, 3, 4, 5);
list.removeIf(Predicates.lessThan(3));
Assert.assertEquals(Lists.mutable.of(3, 4, 5), list);
С синтаксисом Java 8 Lambda это можно записать следующим образом:
MutableList<Integer> list = Lists.mutable.of(1, 2, 3, 4, 5);
list.removeIf(Predicates.cast(integer -> integer < 3));
Assert.assertEquals(Lists.mutable.of(3, 4, 5), list);
Здесь необходим вызов Predicates.cast(), потому что по умолчанию removeIf был добавлен метод removeIf на интерфейсе java.util.Collection в Java 8.
Примечание. Я - коммиттер для Коллекции Eclipse .
Это работает:
Iterator<Integer> iter = l.iterator();
while (iter.hasNext()) {
if (iter.next().intValue() == 5) {
iter.remove();
}
}
Я предположил, что поскольку цикл foreach является синтаксическим сахаром для итерации, использование итератора не помогло бы ... но оно дает вам эту функциональность .remove(). [ ! d1]
В ответ на @assylias вы также можете использовать новый поток api, если используете Java 8:
List<Integer> l = Arrays.asList(4, 5, 6);
static boolean condition(Integer i) {
return i == 5;
}
static Predicate<Integer> predicate = YourClassName::condition;
l.stream()
.filter(predicate.negate())
.forEach(System.out::println);
Если вы инвертируете условие, решение еще более кратким, поскольку вам не нужно negate() предикат, что позволяет использовать только ссылку на метод:
List<Integer> l = Arrays.asList(4, 5, 6);
static boolean condition(Integer i) {
return i != 5; // <-- condition has been negated
}
l.stream()
.filter(YourClassName::condition)
.forEach(System.out::println);
Одна из красавиц этого заключается в том, что поток оценивается лениво, т. е. операция filter() фактически не оценивается до тех пор, пока она не будет использована терминальной операцией, такой как forEach(). Подробнее об этом можно узнать в ответе Oracle .
Поскольку вопрос уже ответил, лучший способ - использовать метод удаления объекта итератора, я хотел бы перейти к особенностям места, где выдается ошибка "java.util.ConcurrentModificationException".
Каждый класс коллекции имеет частный класс, который реализует интерфейс Iterator и предоставляет такие методы, как next(), remove() и hasNext().
Следующий код выглядит примерно так:
public E next() {
checkForComodification();
try {
E next = get(cursor);
lastRet = cursor++;
return next;
} catch(IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
Здесь метод checkForComodification реализован как
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
Итак, как вы можете видеть, если вы явно попытаетесь удалить элемент из коллекции. Это приводит к тому, что modCount отличается от expectedModCount, что приводит к исключению ConcurrentModificationException.
public static void main(String[] args) {
Collection<Integer> l = new CopyOnWriteArrayList<Integer>();
for (int i=0; i < 10; ++i) {
l.add(new Integer(4));
l.add(new Integer(5));
l.add(new Integer(6));
}
for (Integer i : l) {
if (i.intValue() == 5) {
l.remove(i);
}
}
System.out.println(l);
}
A ListIterator позволяет добавлять или удалять элементы в списке. Предположим, у вас есть список объектов Car:
List<Car> cars = ArrayList<>();
// add cars here...
for (ListIterator<Car> carIterator = cars.listIterator(); carIterator.hasNext(); )
{
if (<some-condition>)
{
carIterator().remove()
}
else if (<some-other-condition>)
{
carIterator().add(aNewCar);
}
}
Люди утверждают, что нельзя удалить из коллекции, которая повторяется в цикле foreach. Я просто хотел указать, что это технически некорректно и точно описывает (я знаю, что вопрос OP настолько продвинут, чтобы избежать этого, чтобы понять это) код, лежащий в основе этого предположения:
for (TouchableObj obj : untouchedSet) { // <--- This is where ConcurrentModificationException strikes
if (obj.isTouched()) {
untouchedSet.remove(obj);
touchedSt.add(obj);
break; // this is key to avoiding returning to the foreach
}
}
Это не значит, что вы не может удалить из итерированного Colletion, а затем продолжить, после чего вы не сможете продолжить итерацию. Следовательно, break в коде выше.
Извиняется, если этот ответ является несколько специализированным прецедентом и более подходит для исходной нити, из которой я приехал сюда, этот отмечен как дубликат (несмотря на это поток, появляющийся более нюансированным) этого и заблокированного.
В таких случаях общий трюк (был?) для возврата назад:
for(int i = l.size() - 1; i >= 0; i --) {
if (l.get(i) == 5) {
l.remove(i);
}
}
Тем не менее, я более чем счастлив, что у вас есть лучшие способы в Java 8, например. removeIf или filter в потоках.
С традиционным для цикла
ArrayList<String> myArray = new ArrayList<>();
for (int i = 0; i < myArray.size(); ) {
String text = myArray.get(i);
if (someCondition(text))
myArray.remove(i);
else
i++;
}
Вы можете перебирать список, используя for-loop, и вам нужно вызвать list.remove (0). Вам нужно с жестким кодом индексировать индексный параметр remove с нулем. См. Также этот ответ:
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
int list_size = list.size();
for (int i = 0; i < list_size; i++) {
list.remove(0);
}
В случае ArrayList: remove (int index) - если (индекс - позиция последнего элемента), он избегает без System.arraycopy() и не занимает времени для этого.
Время arraycopy увеличивается, если (индекс уменьшается), кстати, элементы списка также уменьшаются!
лучший эффективный способ удаления - удаление его элементов в порядке убывания: while(list.size()>0)list.remove(list.size()-1); // принимает O (1) while(list.size()>0)list.remove(0); // принимает O (факториал ( n))
//region prepare data
ArrayList<Integer> ints = new ArrayList<Integer>();
ArrayList<Integer> toRemove = new ArrayList<Integer>();
Random rdm = new Random();
long millis;
for (int i = 0; i < 100000; i++) {
Integer integer = rdm.nextInt();
ints.add(integer);
}
ArrayList<Integer> intsForIndex = new ArrayList<Integer>(ints);
ArrayList<Integer> intsDescIndex = new ArrayList<Integer>(ints);
ArrayList<Integer> intsIterator = new ArrayList<Integer>(ints);
//endregion
// region for index
millis = System.currentTimeMillis();
for (int i = 0; i < intsForIndex.size(); i++)
if (intsForIndex.get(i) % 2 == 0) intsForIndex.remove(i--);
System.out.println(System.currentTimeMillis() - millis);
// endregion
// region for index desc
millis = System.currentTimeMillis();
for (int i = intsDescIndex.size() - 1; i >= 0; i--)
if (intsDescIndex.get(i) % 2 == 0) intsDescIndex.remove(i);
System.out.println(System.currentTimeMillis() - millis);
//endregion
// region iterator
millis = System.currentTimeMillis();
for (Iterator<Integer> iterator = intsIterator.iterator(); iterator.hasNext(); )
if (iterator.next() % 2 == 0) iterator.remove();
System.out.println(System.currentTimeMillis() - millis);
//endregion
для индексной петли: 1090 мсек для индекса desc: 519 мс --- лучший для итератора: 1043 мс Сделайте копию существующего списка и перейдете к новой копии.
for (String str : new ArrayList<String>(listOfStr))
{
listOfStr.remove(/* object reference or index */);
}
Вы можете либо использовать итератор прямо так, как вы упомянули, либо сохранить вторую коллекцию и добавить каждый элемент, который хотите удалить, в новую коллекцию, а затем удалитьAll в конце. Это позволяет вам использовать безопасность типа для каждого цикла за счет увеличения использования памяти и времени процессора (не должно быть огромной проблемой, если у вас нет действительно больших списков или действительно старого компьютера)
public static void main(String[] args)
{
Collection<Integer> l = new ArrayList<Integer>();
Collection<Integer> itemsToRemove = new ArrayList<Integer>();
for (int i=0; i < 10; ++i) {
l.add(new Integer(4));
l.add(new Integer(5));
l.add(new Integer(6));
}
for (Integer i : l)
{
if (i.intValue() == 5)
itemsToRemove.add(i);
}
l.removeAll(itemsToRemove);
System.out.println(l);
}
for (Integer i : l)
{
if (i.intValue() == 5){
itemsToRemove.add(i);
break;
}
}
Ловушка - это удаление элемента из списка, если вы пропустите внутренний вызов iterator.next (). он все еще работает! Хотя я не предлагаю писать такой код, он помогает понять концепцию: -)
Cheers!
У меня есть предложение для проблемы выше. Нет необходимости в дополнительном списке или дополнительном времени. Пожалуйста, найдите пример, который будет делать то же самое, но по-другому.
//"list" is ArrayList<Object>
//"state" is some boolean variable, which when set to true, Object will be removed from the list
int index = 0;
while(index < list.size()) {
Object r = list.get(index);
if( state ) {
list.remove(index);
index = 0;
continue;
}
index += 1;
}
Это позволит избежать исключения параллелизма.
ConcurrentHashMap или ConcurrentLinkedQueue или ConcurrentSkipListMap могут быть еще одним вариантом, потому что они никогда не будут бросать любое ConcurrentModificationException, даже если вы удалите или добавите элемент.