LinkedList: удалить объект
Является ли это допустимым способом поиска и удаления элемента из LinkedList в Java с использованием для каждого цикла, возможно ли возникновение несоответствия:
for(ObjectType ob : obList) {
if(ob.getId() == id) {
obList.remove(ob);
break;
}
}
Ответы
Ответ 1
Другие упомянули действительную точку, что обычно это не то, как вы remove
объект из коллекции. ОДНАКО, в этом случае это прекрасно, так как вы break
вышли из цикла после того, как вы remove
.
Если вы хотите продолжить повторение после remove
, вам нужно использовать итератор. В противном случае вы получите ConcurrentModificationException
или в более общем случае, поведение undefined.
Итак, да, , если вы break
вышли из foreach
после remove
, вы будете в порядке.
Тем, кто говорит, что это не удастся, потому что вы не можете изменить коллекцию в foreach
- это правда, только если вы хотите продолжать повторять. Это не тот случай, поэтому этот ярлык в порядке.
A ConcurrentModificationException
проверяется и выдается итератором. Здесь, после remove
(который квалифицируется как параллельная модификация), вы break
выходите из цикла. Итератор даже не имеет возможности его обнаружить.
Может быть, лучше добавить комментарий к break
, почему это абсолютно необходимо и т.д., потому что , если этот код впоследствии будет изменен для продолжения итерации после remove
, он будет терпеть неудачу.
Я бы рассматривал эту идиому, похожую на goto
(точнее, помеченную как break
/continue
): сначала это может показаться неправильным, но при разумном использовании это делает более чистый код.
Ответ 2
Лучше всего использовать итератор и использовать метод удаления при поиске объекта путем итерации по коллекции, чтобы удалить его. Это потому, что
- Коллекция может быть, например, связанным списком (и в вашем случае это), метод удаления которого означает поиск объекта снова и снова, и этот поиск может иметь сложность O (n).
- Вы не можете продолжить итерацию после удаления, если не используете метод удаления итератора. Сейчас вы удаляете первое вхождение - в будущем вам может понадобиться удалить все совпадающие вхождения, и тогда вам придется переписать цикл.
Я рекомендую, в принципе, отказаться от улучшенного и использовать что-то вроде этого:
for(Iterator<ObjectType> it=obList.iterator(); it.hasNext(); ) {
if(it.next().getId()==id) {
it.remove();
break;
}
}
Таким образом, вы не делаете предположений о базовом списке, который может измениться в будущем.
Сравните код для удаления последней записи, вызванной удалением итератора (форматирование Sun):
private E remove(Entry<E> e) {
if (e == header)
throw new NoSuchElementException();
E result = e.element;
e.previous.next = e.next;
e.next.previous = e.previous;
e.next = e.previous = null;
e.element = null;
size--;
modCount++;
return result;
}
против того, что должен сделать remove (Object):
public boolean remove(Object o) {
if (o==null) {
for (Entry<E> e = header.next; e != header; e = e.next) {
if (e.element==null) {
remove(e);
return true;
}
}
} else {
for (Entry<E> e = header.next; e != header; e = e.next) {
if (o.equals(e.element)) {
remove(e);
return true;
}
}
}
return false;
}
Ответ 3
Вы должны использовать iterator.remove()
:
Удаляет из базовой коллекции последний элемент, возвращаемый итератор (дополнительная операция). Эта метод можно вызвать только один раз за звоните в следующий. Поведение итератор не указан, если базовая коллекция модифицирована пока итерация продолжается любым способом, кроме как путем вызова этого Метод.
Ответ 4
Edit: В самом деле, это не провалится из-за перерыва. Подробнее см. В описании полигенного смазочного материала.
Однако это опасный способ сделать. Чтобы одновременно выполнять итерацию и изменение коллекции на Java, вы должны использовать объект "ListIterator" и использовать собственные методы "add()" и "remove()" итератора, а не использовать те, которые содержатся в коллекции.
Вы можете проверить java-документ для классов "java.util.Iterator" и "java.util.ListIterator"
Ответ 5
Попробуйте что-то вроде этого:
Iterator<ObjectType> iter = obList.iterator();
while (iter.hasNext()) {
ObjectType ob = iter.next();
if(ob.getId() == id) {
iter.remove();
break;
}
}
Это одно из последних мест, где Iterator не может быть заменен циклом foreach.
Ответ 6
Чтобы избежать ConcurrentModifiationException, вы можете сделать:
final Iterator<ObjectType> i = obList.iterator();
while (i.hasNext()) {
if (i.next().getId() == id) {
i.remove();
}
}
или
for (int i = 0; i < obList.size(); i++) {
if (obList[i].getId() == id) {
obList.remove(i);
}
}
Я бы предпочел первый. Обработка индексов более ошибочна, и итератор может быть эффективно реализован. И первое предложение работает с Iterable, а второе требует List.
Ответ 7
A CopyOnWriteArrayList
может быть тем, что вы ищете. Когда выполняются мутационные операции, создается копия базового массива. Это позволяет изменять элементы списка внутри внутри каждого цикла. Помните, что это не связанный список и может быть довольно неэффективным.
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class Main {
public static void main(String[] args) {
List<String> myList = new CopyOnWriteArrayList<String>();
myList.add("a");
myList.add("b");
myList.add("c");
// Will print [a, b, c]
System.out.println(myList);
for (String element : myList) {
if (element.equals("a")) {
myList.remove(element);
}
}
// Will print [b, c]
System.out.println(myList);
}
}
Ответ 8
Вышеупомянутый второй цикл должен быть немного изменен
for (int i = 0; i < obList.size(); ) {
if (obList.get(i).getId() == id) {
obList.remove(i);
continue
}
++i;
}
или
for (int i = obList.size() - 1; i >= 0; --i) {
if (obList.get(i).getId() == id) {
obList.remove(i);
}
}