Мы все знаем, что вы не можете этого сделать:
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
.
Iterator.remove()
безопасен, вы можете использовать его так:
List<String> list = new ArrayList<>();
// This is a clever way to create the iterator and call iterator.hasNext() like
// you would do in a while-loop. It would be the same as doing:
// Iterator<String> iterator = list.iterator();
// while (iterator.hasNext()) {
for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
String string = iterator.next();
if (string.isEmpty()) {
// Remove the current element from the iterator and the list.
iterator.remove();
}
}
Обратите внимание, что Iterator.remove()
- единственный безопасный способ изменить коллекцию во время итерации; поведение не определено, если базовая коллекция изменена любым другим способом во время выполнения итерации.
Источник: docs.oracle> Интерфейс коллекции
И точно так же, если у вас есть ListIterator
и вы хотите добавить элементы, вы можете использовать ListIterator#add
, по той же причине, по которой вы можете использовать Iterator#remove
- он предназначен для этого.
В вашем случае вы пытались удалить из списка, но те же ограничения применяются, если вы пытаетесь put
в Map
при переборе ее содержимого.
Это работает:
Iterator<Integer> iter = l.iterator();
while (iter.hasNext()) {
if (iter.next().intValue() == 5) {
iter.remove();
}
}
Я предположил, что поскольку цикл foreach является синтаксическим сахаром для итерации, использование итератора не поможет... но он предоставляет вам эту .remove()
.
С помощью Java 8 вы можете использовать новый метод removeIf
. Применяется к вашему примеру:
Collection<Integer> coll = new ArrayList<Integer>();
//populate
coll.removeIf(i -> i.intValue() == 5);
Поскольку вопрос уже был отвечен, лучший способ - использовать метод удаления объекта итератора, я хотел бы перейти к особенностям места, где выдается ошибка "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
.
Вы можете либо использовать итератор прямо так, как вы упомянули, либо сохранить вторую коллекцию и добавить каждый элемент, который хотите удалить, в новую коллекцию, а затем удалить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(int i = l.size() - 1; i >= 0; i --) {
if (l.get(i) == 5) {
l.remove(i);
}
}
Тем не менее, я более чем счастлив, что у вас есть лучшие способы в Java 8, например. removeIf
или filter
в потоках.
ArrayList
или подобных коллекций.
Тот же ответ, что и Claudius с циклом for:
for (Iterator<Object> it = objects.iterator(); it.hasNext();) {
Object object = it.next();
if (test) {
it.remove();
}
}
С Коллекции Eclipse (ранее Коллекции GS), метод 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()
необходим здесь, потому что в интерфейсе java.util.Collection
в Java 8 был добавлен метод removeIf
по умолчанию.
Примечание. Я - коммиттер для Коллекции Eclipse.
С традиционным для цикла
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++;
}
i++
в защите цикла, а не в теле цикла.
Сделайте копию существующего списка и перейдите по новой копии.
for (String str : new ArrayList<String>(listOfStr))
{
listOfStr.remove(/* object reference or index */);
}
Люди утверждают, что один не может удалить из коллекции, которая повторяется в цикле foreach. Я просто хотел указать, что это технически некорректно и точно описать (я знаю, что вопрос ОП настолько продвинут, чтобы исключить его знание) код, лежащий в основе этого предположения:
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
в коде выше.
Извиняется, если этот ответ является несколько специализированным прецедентом и более подходит для исходной темы, из которой я приехал сюда, этот отмечен как дубликат (несмотря на это поток, появляющийся более нюансированным) этого и заблокированного.
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);
}
}
previous
метод.
Лучший способ (рекомендуется) - использование пакета java.util.Concurrent. Используя этот пакет, вы можете легко избежать этого исключения. см. Модифицированный код
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);
}
ConcurrentHashMap или ConcurrentLinkedQueue или ConcurrentSkipListMap может быть другой опцией, потому что они никогда не будут бросать любое ConcurrentModificationException, даже если вы удалите или добавите элемент.
java.util.concurrent
. Некоторые другие похожие / общие CopyOnWriteArrayList
использования из этого пакета - CopyOnWriteArrayList
& CopyOnWriteArraySet
[но не ограничиваются ими].
ConcurrentModificationException
, их использование в расширенном цикле -for может по-прежнему вызывать проблемы с индексированием (т. Е. По-прежнему пропускать элементы или IndexOutOfBoundsException
...)
У меня есть предложение для проблемы выше. Нет необходимости в дополнительном списке или дополнительном времени. Пожалуйста, найдите пример, который будет делать то же самое, но по-другому.
//"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;
}
Это позволит избежать исключения Concurrency.
ArrayList
и, следовательно, не может полагаться на get()
. В противном случае, вероятно, хороший подход, хотя.
Collection
- интерфейс Collection
не включает get
. (Хотя интерфейс List
FWIW включает «get»).
Я знаю, что этот вопрос предполагает только Collection
, а не конкретный List
. Но для тех, кто читает этот вопрос и действительно работает со ссылкой на List
, вы можете вместо этого использовать исключение ConcurrentModificationException
с некоторое while
-loop (при его изменении), если вы хотите избежать Iterator
(либо если вы хотите избежать его вообще, либо специально избегайте этого, чтобы добиться порядка зацикливания, отличного от остановки до конца в каждом элементе [что, я считаю, является единственным порядком, который может выполнять сам Iterator
]):
* Обновление: см. Комментарии ниже, которые поясняют, что аналогичное также возможно с традиционным для -loop.
final List<Integer> list = new ArrayList<>();
for(int i = 0; i < 10; ++i){
list.add(i);
}
int i = 1;
while(i < list.size()){
if(list.get(i) % 2 == 0){
list.remove(i++);
} else {
i += 2;
}
}
Нет ConcurrentModificationException из этого кода.
Там мы видим, что цикл не начинается в начале и не останавливается на каждом элементе (что, как я полагаю, сам по себе Iterator
не может).
FWIW мы также видим, get
называют по list
, который не может быть сделано, если его ссылка была просто Collection
(вместо более определенного List
-type из Collection
) - List
интерфейс включает в себя get
, но Collection
интерфейс не делает. Если бы не эта разница, тогда ссылка на list
могла бы вместо этого быть Collection
[и, следовательно, технически этот ответ был бы тогда прямым ответом, а не тангенциальным ответом].
FWIWW тот же код по-прежнему работает после изменения, чтобы начать с начала и до остановки на каждом элементе (как порядок Iterator
):
final List<Integer> list = new ArrayList<>();
for(int i = 0; i < 10; ++i){
list.add(i);
}
int i = 0;
while(i < list.size()){
if(list.get(i) % 2 == 0){
list.remove(i);
} else {
++i;
}
}
Я знаю, что этот вопрос слишком старый, чтобы быть о Java 8, но для тех, кто использует Java 8, вы можете легко использовать removeIf():
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));
}
l.removeIf(i -> i.intValue() == 5);
Пример модификации потока безопасной коллекции:
public class Example {
private final List<String> queue = Collections.synchronizedList(new ArrayList<String>());
public void removeFromQueue() {
synchronized (queue) {
Iterator<String> iterator = queue.iterator();
String string = iterator.next();
if (string.isEmpty()) {
iterator.remove();
}
}
}
}
List<String> strings=new ArrayList<String>(){};
while(strings.size() > 0) {
String str = strings.remove(0);
}
for (Integer i : l)
{
if (i.intValue() == 5){
itemsToRemove.add(i);
break;
}
}
Ловушка - это удаление элемента из списка, если вы пропустите внутренний вызов iterator.next(). он все еще работает! Хотя я не предлагаю писать такой код, это помогает понять концепцию, лежащую в ее основе: -)
Ура!
В случае 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 (factorial (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
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
Лямбда-выражения и методы Collection в Jdk 8 включены в Handy и добавляют синтаксический сахар sugar.
Метод removeIf
просматривает коллекцию и фильтрует ее с помощью Predicate. Предикат является функцией аргумента, который возвращает логическое значение... Так же, как boolean _bool = (str) → str.equals("text");
В дополнение к @assylias
answer вы также можете использовать новый Stream 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 Tutorial.
Вы можете перебирать список, используя 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);
}
это может быть не лучшим образом, но для большинства небольших случаев это должно быть приемлемым:
"создайте второй пустой массив и добавьте только те, которые вы хотите сохранить"
Я не помню, где я это читал... для справедливости я сделаю эту вики в надежде, что кто-то найдет ее или просто не заработает репутацию, которую я не заслуживаю.