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

Это, допустимый способ найти и удалить объект из LinkedList в Java с помощью для каждого цикла, является этим возможный, что несоответствие может возникнуть:

for(ObjectType ob : obList) {
  if(ob.getId() == id) {
    obList.remove(ob);
    break;
   }
}
15
задан Xolve 26 February 2010 в 17:46
поделиться

7 ответов

Другие указывали на то, что обычно это не так, как вы удаляете объект из коллекции. КАЧЕСТВЕННО, в данном случае это нормально, так как Вы вырываете из цикла после того, как Вы удаляете.

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

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


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

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

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

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

16
ответ дан 1 December 2019 в 01:30
поделиться

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

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

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

1
ответ дан 1 December 2019 в 01:30
поделиться

Другим вариантом может быть использование массива boost:: необязательный < Foo > :

boost::optional<Foo> foos[10]; // No construction takes place
                               // (similar to vector::reserve)

foos[i] = Foo(3); // Actual construction

Одно из предостережений состоит в том, что доступ к элементам должен осуществляться с помощью синтаксиса указателя:

bar(*foos[2]); // "bar" is a function taking a "Foo"

std::cout << foos[3]->baz(); // "baz" is a member of "Foo"

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

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

-121--2750484-

В некоторых (большинстве?) систем трубы по умолчанию используют буферизацию ввода-вывода. Поместите инструкцию

$handle->autoflush(1);

в функцию myPipe .

Но даже когда буферизация отключена, Perl по-прежнему не сбрасывается, кроме как после новой строки. Таким образом, может потребоваться, чтобы дочерний процесс включил в вывод новую строку.


Обновление: Тестирование кода (Cygwin, perl 5,10,0, YMMV), я вижу проблему в отсутствии новой строки в дочернем выводе, а не в том, включена ли автоматическая очистка при создании трубы.

-121--3832323-

Чтобы избежать ConcurrentModifyException , можно сделать следующее:

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, в то время как второе требует списка.

1
ответ дан 1 December 2019 в 01:30
поделиться

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

  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;
}
7
ответ дан 1 December 2019 в 01:30
поделиться

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

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

6
ответ дан 1 December 2019 в 01:30
поделиться

Edit: Действительно, это не приведет к сбою благодаря прерыванию. Подробности см. в ответе polygenelubricant.

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

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

4
ответ дан 1 December 2019 в 01:30
поделиться

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

}
0
ответ дан 1 December 2019 в 01:30
поделиться
Другие вопросы по тегам:

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