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