Вызов удаления в цикле foreach в Java

В Java разрешено ли вызывать удаление в коллекции при повторении через коллекцию с использованием цикла foreach? Например:

List<String> names = ....
for (String name : names) {
   // Do something
   names.remove(name).
}

В качестве дополнения, является ли законным удаление элементов, которые еще не были повторены? Например,

//Assume that the names list as duplicate entries
List<String> names = ....
for (String name : names) {
    // Do something
    while (names.remove(name));
}
+472
источник поделиться
11 ответов

Чтобы безопасно удалить из коллекции, итерации по ней, вы должны использовать Iterator.

Например:

List<String> names = ....
Iterator<String> i = names.iterator();
while (i.hasNext()) {
   String s = i.next(); // must be called before you can call i.remove()
   // Do something
   i.remove();
}

Из Документация по Java:

Итераторы, возвращаемые этим классом итератором и listIterator методы не работают быстро: если список структурно модифицирован в любом время после создания итератора, любым способом, за исключением итератор самостоятельно удаляет или добавляет методы, итератор будет бросать ConcurrentModificationException. Таким образом, перед лицом параллельных модификации, итератор выходит из строя быстро и чисто, а не рискуя произвольным, недетерминированным поведением в неопределенное время в будущем.

Возможно, для многих новичков непонятно, что итерация по списку с использованием конструкций for/foreach неявно создает итератор, который обязательно недоступен. Эту информацию можно найти здесь

+710
источник

Вы не хотите этого делать. Это может вызвать поведение undefined в зависимости от коллекции. Вы хотите напрямую использовать Iterator. Хотя для каждой конструкции используется синтаксический сахар и на самом деле используется итератор, он скрывает его от вашего кода, поэтому вы не можете получить к нему доступ, чтобы вызвать Iterator.remove.

Поведение итератора неопределенный, если основной коллекция изменяется, итерация продолжается каким-либо образом кроме вызова этого метода.

Вместо этого напишите свой код:

List<String> names = ....
Iterator<String> it = names.iterator();
while (it.hasNext()) {

    String name = it.next();
    // Do something
    it.remove();
}

Обратите внимание, что код вызывает Iterator.remove, а не List.remove.

Добавление:

Даже если вы удаляете элемент, который еще не был итерирован, вы все равно не хотите изменять коллекцию, а затем использовать Iterator. Он может модифицировать коллекцию таким образом, что это удивительно, и влияет на будущие операции на Iterator.

+147
источник
другие ответы

Связанные вопросы


Похожие вопросы

Конструкция java "расширенного цикла" заключалась в том, чтобы не подвергать итератору код, но единственным способом безопасного удаления элемента является доступ к итератору. Поэтому в этом случае вам нужно сделать это в старой школе:

 for(Iterator<String> i = names.iterator(); i.hasNext();) {
       String name = i.next();
       //Do Something
       i.remove();
 }

Если в реальном коде цикл повышенного цикла действительно стоит того, вы можете добавить элементы во временную коллекцию и вызывать removeAll в списке после цикла.

EDIT (re addendum): Нет, изменение списка каким-либо образом вне метода iterator.remove(), в то время как итерация вызовет проблемы. Единственный способ обойти это - использовать CopyOnWriteArrayList, но это действительно предназначено для проблем concurrency.

Самый дешевый (с точки зрения строк кода) способ удаления дубликатов - это сброс списка в LinkedHashSet (а затем обратно в список, если вам нужно). Это сохраняет порядок вставки при удалении дубликатов.

+47
источник
for (String name : new ArrayList<String>(names)) {
    // Do something
    names.remove(nameToRemove);
}

Вы клонируете список names и итерации через клон во время удаления из исходного списка. Немного чище, чем верхний ответ.

+38
источник

Я не знал об итераторах, но вот что я делал до сегодняшнего дня, чтобы удалить элементы из списка внутри цикла:

List<String> names = .... 
for (i=names.size()-1;i>=0;i--) {    
    // Do something    
    names.remove(i);
} 

Это всегда работает и может использоваться на других языках или структурах, не поддерживающих итераторы.

+22
источник

Да, вы можете использовать цикл for-each, Для этого вам необходимо сохранить отдельный список для удаления элементов удаления, а затем удалить этот список из списка имен с помощью метода removeAll(),

List<String> names = ....

// introduce a separate list to hold removing items
List<String> toRemove= new ArrayList<String>();

for (String name : names) {
   // Do something: perform conditional checks
   toRemove.add(name);
}    
names.removeAll(toRemove);

// now names list holds expected values
+16
источник

Те, кто говорит, что вы не можете безопасно удалить элемент из коллекции, кроме как через Iterator, не совсем корректны, вы можете безопасно использовать одну из параллельных коллекций, таких как ConcurrentHashMap.

+3
источник

Убедитесь, что это не запах кода. Можно ли отменить логику и быть "включенным", а не "эксклюзивным"?

List<String> names = ....
List<String> reducedNames = ....
for (String name : names) {
   // Do something
   if (conditionToIncludeMet)
       reducedNames.add(name);
}
return reducedNames;

Ситуация, которая привела меня к этой странице, включала старый код, который перебирал List с помощью indecies для удаления элементов из списка. Я хотел реорганизовать его для использования стиля foreach.

Он зациклился на весь список элементов, чтобы проверить, какие у пользователя были права доступа, и удалил те, у которых не было разрешения из списка.

List<Service> services = ...
for (int i=0; i<services.size(); i++) {
    if (!isServicePermitted(user, services.get(i)))
         services.remove(i);
}

Отменить это и не использовать remove:

List<Service> services = ...
List<Service> permittedServices = ...
for (Service service:services) {
    if (isServicePermitted(user, service))
         permittedServices.add(service);
}
return permittedServices;

Когда "удалить" будет предпочтительнее? Одним из соображений является наличие большого списка или дорогостоящего "добавления" в сочетании с несколькими удаленными по сравнению с размером списка. Возможно, было бы более эффективно делать только несколько удалений, а не добавлять много. Но в моем случае ситуация не заслуживала такой оптимизации.

+3
источник
  • Попробуйте это 2. и измените условие на "WINTER", и вы спросите:
public static void main(String[] args) {
  Season.add("Frühling");
  Season.add("Sommer");
  Season.add("Herbst");
  Season.add("WINTER");
  for (String s : Season) {
   if(!s.equals("Sommer")) {
    System.out.println(s);
    continue;
   }
   Season.remove("Frühling");
  }
 }
+1
источник

Лучше использовать Iterator, когда вы хотите удалить элемент из списка

потому что исходный код удаления -

if (numMoved > 0)
    System.arraycopy(elementData, index+1, elementData, index,
             numMoved);
elementData[--size] = null;

поэтому, если вы удалите элемент из списка, список будет реструктурирован, индекс другого элемента будет изменен, это может привести к тому, что вы хотите выполнить.

+1
источник

Использование

.remove() для Interator или

Используйте

CopyOnWriteArrayList

-3
источник

Посмотрите другие вопросы по меткам или Задайте вопрос