简体   繁体   English

在迭代列表时从列表中删除项目

[英]Removing items from a list while iterating over it

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 .我已经尝试了上述想法并收到了异常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.我希望有人能指出我如何在迭代时正确地从方法调用中改变列表的正确方向。

The best solution is not to maintain the currently playing song resp.最好的解决方案是不维护当前正在播放的歌曲。 the next song to play via an Iterator .通过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. AbstractList专门设计用于在几个方法之上提供集合操作,因此我们只需要实现size()get(int)即可获得可读列表,并通过提供add(int, Song)remove(int)set(int, Song)我们已经完成了支持所有修改操作所需的一切。 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() .该列表支持指向当前播放位置的单个指针,可以通过nextSong()对其进行迭代。 When reaching the end, it will return null and reset the pointer, so that the next query will start again.当到达末尾时,它将返回null并重置指针,以便下一个查询重新开始。 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). addremove方法将调整指针,以便不再播放已经播放的歌曲(除非重新启动整个列表)。

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.基于set的修改不适应指针,这意味着对列表进行sort时不会发生任何有意义的事情,一些策略是可以想象的,但至少当列表有重复时,不存在完美的行为。 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.至少,永远不会有例外。

Use an Iterator and call its remove() method:使用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
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM