1

I've seen many questions regarding this topics but none of the answers that really quite my usecase (Unless I've interpreted it wrong.). Is it possible to remove an item from a list while an iterator is iterating over it?

What I'm trying to achieve is having a queue with an audioplayer. The iterator iterates over the queue and blocks while playing the song. Songs can be added to or deleted from the queue while it is already playing.

I've tried the above idea and received an exception ConcurrentModificationException . I've also read that it's a bad practice to mutate a collection while an iterator is iterating over it. I'm hoping someone can point me into the right direction on how to properly mutate a list from a method call while iterating over it.

  • 4
    Does this answer your question? [Remove elements from collection while iterating](https://stackoverflow.com/questions/10431981/remove-elements-from-collection-while-iterating) – Amongalen Feb 11 '20 at 15:15
  • You can use a temp list and while iterating add only those values which are desired , and later assign this temp list to original list – Adnan Feb 11 '20 at 15:19
  • @Adnan But the iterator doesn't know if the value is desired until it reaches it. And it only goes to a next/previous when requested or when the 'audio' is done playing. So until the iterator reaches the object that is 'marked for removal' it's still in the list. – Marcel Oostebring Feb 11 '20 at 15:26
  • Oh nevermind question got closed because someone thought it's a duplicate except that it's not. – Marcel Oostebring Feb 11 '20 at 15:29
  • 1
    I would suggest writing this as a custom class, without implementing `Iterable` or `Iterator`. Store the songs in an ArrayList and keep track of the index of the currently-playing song; to get the next one, increment that index and return the song at the new index. When you add or remove a song, update the index accordingly. Using `Iterable`/`Iterator` makes it too complicated, because it's the main class which knows how to update the indices, not the iterator(s) themselves; and it seems you don't need to have multiple concurrent iterators over the same list. – kaya3 Feb 11 '20 at 17:49
  • 1
    @kaya3 I was already writing [a similar approach](https://stackoverflow.com/a/60174756/2711488), but thanks to `AbstractList` which has all the methods and features re-routed through a few methods, it’s possible to support the entire Collection API and still update the index correctly. – Holger Feb 11 '20 at 17:57

2 Answers2

3

The best solution is not to maintain the currently playing song resp. the next song to play via an Iterator. Instead, you can create a specialized list which knows how to adapt this pointer on modifications.

Such a class could look like

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 is specifically designed to provide the collection operations atop a few methods, so we only need to implement size() and get(int) to have a readable list and by providing add(int, Song), remove(int), and set(int, Song) we did already everything needed to support all modification operations. The other methods are only provided to improve the performance, the inherited methods would also work.

The list supports a single pointer to a current play position, which can be iterated via nextSong(). When reaching the end, it will return null and reset the pointer, so that the next query will start again. The add and remove methods will adapt the pointer such that an already played song won’t be played again (unless restarting the entire list).

set based modifications do not adapt the pointer, which implies that nothing meaningful will happen when you sort the list, some policies are imaginable, but at least when the list has duplicates, no perfect behavior exists. When comparing with other player software, no-one seems to expect perfect behavior when the list is turned up-side-down while playing. At least, there will never be an exception.

Holger
  • 285,553
  • 42
  • 434
  • 765
2

Use an Iterator and call its remove() method:

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
    }
}
Bohemian
  • 412,405
  • 93
  • 575
  • 722