Вызов удаления в цикле 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));
}

Ответ 1

Чтобы безопасно удалить из коллекции, итерации по ней, вы должны использовать 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 неявно создает итератор, который обязательно недоступен. Эту информацию можно найти здесь

Ответ 2

Вы не хотите этого делать. Это может вызвать поведение 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.

Ответ 3

Конструкция 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 (а затем обратно в список, если вам нужно). Это сохраняет порядок вставки при удалении дубликатов.

Ответ 4

for (String name : new ArrayList<String>(names)) {
    // Do something
    names.remove(nameToRemove);
}

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

Ответ 5

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

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

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

Ответ 6

Да, вы можете использовать цикл 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

Ответ 7

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

Ответ 8

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

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;

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

Ответ 9

  • Попробуйте это 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");
  }
 }

Ответ 10

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

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

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

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

Ответ 11

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

.remove() для Interator или

Используйте

CopyOnWriteArrayList