Удаление элементов из списка во время итерации по нему - PullRequest
1 голос
/ 11 февраля 2020

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

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

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

Ответы [ 2 ]

3 голосов
/ 11 февраля 2020

Лучшее решение - не поддерживать текущую воспроизводимую песню, соответственно. следующая песня для воспроизведения через 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 список, некоторые политики можно себе представить, но по крайней мере, когда список имеет дубликаты, идеального поведения не существует. При сравнении с другим программным обеспечением плеера никто не ожидает идеального поведения, когда список переворачивается во время игры. По крайней мере, никогда не будет исключения.

2 голосов
/ 11 февраля 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
    }
}
...