在遍历列表时从列表中删除项目
Removing items from a list while iterating over it
我已经看到很多关于这个主题的问题,但是 none 的答案真的很适合我的用例(除非我解释错了。)。是否可以在迭代器迭代时从列表中删除一个项目?
我想要实现的是有一个带有音频播放器的队列。迭代器在播放歌曲时迭代队列和阻塞。队列正在播放时,可以在队列中添加或删除歌曲。
我已经尝试过上述想法并收到异常 ConcurrentModificationException
。
我还读到,在迭代器迭代集合时改变集合是一种不好的做法。我希望有人能指出正确的方向,告诉我如何在遍历列表时从方法调用中正确地改变列表。
使用 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
}
}
最好的解决办法是不维护当前正在播放的歌曲。通过 Iterator
播放下一首歌曲。相反,您可以创建一个专门的列表,它知道如何在修改时调整此指针。
这样的 class 可能看起来像
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()
迭代。当到达终点时,它会return null
并重置指针,以便重新开始下一次查询。 add
和 remove
方法将调整指针,以便不会再次播放已经播放的歌曲(除非重新启动整个列表)。
基于 set
的修改不适应指针,这意味着当您 sort
列表时不会发生任何有意义的事情,一些策略是可以想象的,但至少当列表有重复时,没有存在完美的行为。与其他播放器软件比较时,似乎没有人期望在播放时上下颠倒列表时的完美行为。至少,永远不会有例外。
我已经看到很多关于这个主题的问题,但是 none 的答案真的很适合我的用例(除非我解释错了。)。是否可以在迭代器迭代时从列表中删除一个项目?
我想要实现的是有一个带有音频播放器的队列。迭代器在播放歌曲时迭代队列和阻塞。队列正在播放时,可以在队列中添加或删除歌曲。
我已经尝试过上述想法并收到异常 ConcurrentModificationException
。
我还读到,在迭代器迭代集合时改变集合是一种不好的做法。我希望有人能指出正确的方向,告诉我如何在遍历列表时从方法调用中正确地改变列表。
使用 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
}
}
最好的解决办法是不维护当前正在播放的歌曲。通过 Iterator
播放下一首歌曲。相反,您可以创建一个专门的列表,它知道如何在修改时调整此指针。
这样的 class 可能看起来像
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()
迭代。当到达终点时,它会return null
并重置指针,以便重新开始下一次查询。 add
和 remove
方法将调整指针,以便不会再次播放已经播放的歌曲(除非重新启动整个列表)。
set
的修改不适应指针,这意味着当您 sort
列表时不会发生任何有意义的事情,一些策略是可以想象的,但至少当列表有重复时,没有存在完美的行为。与其他播放器软件比较时,似乎没有人期望在播放时上下颠倒列表时的完美行为。至少,永远不会有例外。