Удаление элементов из списка во время итерации по нему

Я видел много вопросов по этим темам, но ни один из ответов не подходил для меня (если только я не интерпретировал это неправильно). Можно ли удалить элемент из списка, пока итератор выполняет итерацию по нему?

Я пытаюсь добиться очереди с аудиоплеером. Итератор перебирает очередь и блокирует воспроизведение песни. Песни можно добавлять в очередь или удалять из нее, пока она уже проигрывается.

Я попробовал описанную выше идею и получил исключение ConcurrentModificationException. Я также читал, что мутировать коллекцию, когда итератор выполняет ее итерацию, - плохая практика. Я надеюсь, что кто-то может указать мне правильное направление, как правильно изменить список из вызова метода при повторении его.


person Marcel Oostebring    schedule 11.02.2020    source источник
comment
Отвечает ли это на ваш вопрос? Удалять элементы из коллекции во время итерации   -  person Amongalen    schedule 11.02.2020
comment
Вы можете использовать временный список и во время итерации добавлять только те значения, которые требуются, а позже назначить этот временный список исходному списку.   -  person Adnan    schedule 11.02.2020
comment
@Adnan Но итератор не знает, нужно ли значение, пока не достигнет его. И он переходит к следующему / предыдущему только по запросу или по окончании воспроизведения «звука». Таким образом, пока итератор не достигнет объекта, который «помечен для удаления», он все еще находится в списке.   -  person Marcel Oostebring    schedule 11.02.2020
comment
О да, вопрос был закрыт, потому что кто-то подумал, что это дубликат, но это не так.   -  person Marcel Oostebring    schedule 11.02.2020
comment
Я бы посоветовал написать это как собственный класс без реализации Iterable или Iterator. Сохраните песни в ArrayList и следите за индексом текущей песни; чтобы получить следующий, увеличьте этот индекс и верните песню с новым индексом. При добавлении или удалении песни обновите индекс соответствующим образом. Использование _3 _ / _ 4_ делает его слишком сложным, потому что это основной класс, который знает, как обновлять индексы, а не сами итераторы; и кажется, что вам не нужно иметь несколько одновременных итераторов по одному и тому же списку.   -  person kaya3    schedule 11.02.2020
comment
@ kaya3 Я уже писал аналогичный подход, но благодаря AbstractList, в котором все методы и функции перенаправлены через несколько методов, можно поддерживать весь API коллекции и при этом правильно обновлять индекс.   -  person Holger    schedule 11.02.2020


Ответы (2)


Лучшее решение - не поддерживать песню, воспроизводимую в данный момент, соответственно. следующая песня для воспроизведения через Iterator. Вместо этого вы можете создать специализированный список, который знает, как адаптировать этот указатель к изменениям.

Такой класс мог бы выглядеть как

class SongList extends AbstractList<Song> implements RandomAccess {
    final List<Song> backend = new ArrayList<>();
    int currentSong = -1;

    SongList() {}
    SongList(Collection<? extends Song> c) {
        backend.addAll(c);
    }
    // mandatory query methods

    @Override public int size() {
        return backend.size();
    }
    @Override public Song get(int index) {
        return backend.get(index);
    }

    // the "iterator"
    public Song nextSong() {
        if(++currentSong < size()) {
            return get(currentSong);
        }
        currentSong = -1;
        return null;
    }

    // modifying methods, which will adapt the pointer

    @Override public void add(int index, Song element) {
        backend.add(index, element);
        if(index <= currentSong) currentSong++;
    }
    @Override public Song remove(int index) {
        final Song removed = backend.remove(index);
        if(index <= currentSong) currentSong--;
        return removed;
    }

    @Override
    public boolean addAll(int index, Collection<? extends Song> c) {
        int old = size();
        backend.addAll(index, c);
        if(index <= currentSong) currentSong += size() - old;
        return true;
    }

    @Override protected void removeRange(int fromIndex, int toIndex) {
        backend.subList(fromIndex, toIndex).clear();
        if(fromIndex <= currentSong)
            currentSong = Math.max(fromIndex - 1, currentSong - toIndex + fromIndex);
    }

    // this will not change the pointer

    @Override public Song set(int index, Song element) {
        return backend.set(index, element);
    }

    // query methods overridden for performance

    @Override public boolean contains(Object o) {
        return backend.contains(o);
    }
    @Override public int indexOf(Object o) {
        return backend.indexOf(o);
    }
    @Override public Spliterator<Song> spliterator() {
        return backend.spliterator();
    }
    @Override public void forEach(Consumer<? super Song> action) {
        backend.forEach(action);
    }
    @Override public Object[] toArray() {
        return backend.toArray();
    }
    @Override public <T> T[] toArray(T[] a) {
        return backend.toArray(a);
    }
    @Override public String toString() {
        return backend.toString();
    }
}

AbstractList специально разработан для обеспечения операций сбора поверх нескольких методов, поэтому нам нужно только реализовать size() и get(int), чтобы иметь читаемый список, и, предоставив add(int, Song), remove(int) и set(int, Song), мы уже сделали все необходимое для поддержки всех операций модификации. Другие методы предназначены только для повышения производительности, унаследованные методы также будут работать.

Список поддерживает единственный указатель на текущую позицию воспроизведения, который можно повторять с помощью nextSong(). Достигнув конца, он вернет null и сбросит указатель, так что следующий запрос начнется снова. Методы add и remove адаптируют указатель таким образом, чтобы уже проигранная песня не воспроизводилась снова (если не перезапустить весь список).

Изменения на основе set не адаптируют указатель, что означает, что ничего значимого не произойдет, когда вы sort список, некоторые политики можно вообразить, но, по крайней мере, когда в списке есть дубликаты, идеального поведения не существует. При сравнении с другим программным обеспечением проигрывателя, кажется, никто не ожидает идеального поведения, когда список перевернут во время игры. По крайней мере, исключения никогда не будет.

person Holger    schedule 11.02.2020
comment
В сочетании с тем, что я сделал сегодня ранее, это указывало мне на правильное направление. Спасибо! - person Marcel Oostebring; 12.02.2020

Используйте Iterator и вызовите его метод remove():

List<String> myList = new ArrayList<>();
for (Iterator<String> i = myList.iterator(); i.hasNext();) {
    String next = i.next();
    if (some condition) {
        i.remove(); // removes the current element
    }
}
person Bohemian♦    schedule 11.02.2020
comment
С оговоркой, что не все итераторы списков поддерживают удаление. - person Andy Turner; 11.02.2020
comment
Я хочу редактировать список из вызова метода, а не самого итератора. Итератор должен просто взять предыдущий / следующий объект, когда он будет работать с текущим объектом. - person Marcel Oostebring; 11.02.2020