LinkedList: удалить объект - PullRequest
       10

LinkedList: удалить объект

15 голосов
/ 26 февраля 2010

Является ли это допустимым способом поиска и удаления элемента из LinkedList в Java с использованием a для каждого цикла, возможно ли возникновение несоответствия:

for(ObjectType ob : obList) {
  if(ob.getId() == id) {
    obList.remove(ob);
    break;
   }
}

Ответы [ 8 ]

16 голосов
/ 26 февраля 2010

Другие упоминали правильную точку зрения, что обычно это не то, как вы remove объект из коллекции. ОДНАКО, в этом случае это нормально, так как вы break вне цикла, как только вы remove.

Если вы хотите продолжать итерацию после remove, вам нужно использовать итератор. В противном случае вы получите ConcurrentModificationException или, в более общем случае, неопределенное поведение.

Так что да, , если вы break из foreach после вас remove, у вас все будет в порядке .


Для тех, кто говорит, что это не удастся, потому что вы не можете изменить коллекцию в foreach - это верно, только если вы хотите продолжать итерацию. Это не тот случай, поэтому этот ярлык в порядке.

A ConcurrentModificationException проверяется и генерируется итератором. Здесь, после remove (который считается одновременной модификацией), вы break вышли из цикла. Итератор даже не имеет возможности его обнаружить.

Лучше всего, если вы добавите комментарий к break, почему это абсолютно необходимо и т. Д., Потому что , если этот код будет позже изменен для продолжения итерации после remove, произойдет сбой .

Я бы отнесся к этой идиоме так же, как goto (точнее, с пометкой break / continue): поначалу она может показаться неправильной, но при разумном использовании она делает код чище.

7 голосов
/ 26 февраля 2010

Лучше всего использовать итератор и использовать его метод remove при поиске объекта путем итерации по коллекции, чтобы удалить его. Это потому что

  1. Коллекция может быть, например, связанным списком (и в вашем случае это так), метод удаления которого означает поиск объекта заново, причем поиск может иметь сложность O (n).
  2. Вы не можете продолжить итерацию после удаления, если не используете метод удаления итератора. Прямо сейчас вы удаляете первое вхождение - в будущем вам может потребоваться удалить все совпадающие вхождения, в этом случае вам придется переписать цикл.

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

for(Iterator<ObjectType> it=obList.iterator(); it.hasNext(); ) {
    if(it.next().getId()==id) { 
        it.remove(); 
        break;
        }
    } 

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


Сравните код для удаления последней записи, вызванной итератором remove (форматирование 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;
}
6 голосов
/ 26 февраля 2010

Вы должны использовать iterator.remove():

Удаляет из базовой коллекции последний элемент, возвращаемый итератор (необязательная операция). это метод может быть вызван только один раз за звоните к следующему. Поведение итератор не указан , если базовая коллекция изменена в то время как итерация в процессе любым другим способом, кроме как позвонив по этому способ.

4 голосов
/ 26 февраля 2010

Редактировать: Действительно, он не потерпит неудачу благодаря перерыву. Подробности смотрите в ответе полигеномасляной смазки.

Однако это опасный способ. Чтобы одновременно выполнять итерацию и модифицировать коллекцию в Java, необходимо использовать объект «ListIterator» и использовать собственные методы итератора «add ()» и «remove ()», а не использовать методы в коллекции.

Вы можете проверить документацию по Java для классов "java.util.Iterator" и "java.util.ListIterator"

1 голос
/ 26 февраля 2010

Чтобы избежать исключения 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.

1 голос
/ 26 февраля 2010

Попробуйте что-то вроде этого:

Iterator<ObjectType> iter = obList.iterator();
while (iter.hasNext()) {
  ObjectType ob = iter.next();
  if(ob.getId() == id) {
    iter.remove();
    break;
  }
}

Это одно из последних мест, где итератор не может быть заменен циклом foreach.

0 голосов
/ 13 июля 2011

вышеуказанный второй цикл должен быть немного изменен

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);
    }
}
0 голосов
/ 26 февраля 2010

A CopyOnWriteArrayList может быть то, что вы ищете. Когда выполняются мутативные операции, создается копия базового массива. Это позволяет модифицировать элементы списка внутри цикла for-each. Помните, однако, что это не связанный список и может быть довольно неэффективным.

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);
    }

}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...