[英]Remove elements from collection while iterating
AFAIK, there are two approaches: AFAIK,有两种方法:
For instance,例如,
List<Foo> fooListCopy = new ArrayList<Foo>(fooList); for(Foo foo: fooListCopy){ // modify actual fooList }
and和
Iterator<Foo> itr = fooList.iterator(); while(itr.hasNext()){ // modify actual fooList using itr.remove() }
Are there any reasons to prefer one approach over the other (eg preferring the first approach for the simple reason of readability)?有什么理由更喜欢一种方法而不是另一种(例如,出于可读性的简单原因更喜欢第一种方法)?
Let me give a few examples with some alternatives to avoid a ConcurrentModificationException
.让我举几个例子来避免ConcurrentModificationException
。
Suppose we have the following collection of books假设我们有以下书籍集合
List<Book> books = new ArrayList<Book>(); books.add(new Book(new ISBN("0-201-63361-2"))); books.add(new Book(new ISBN("0-201-63361-3"))); books.add(new Book(new ISBN("0-201-63361-4")));
Collect and Remove收集和删除
The first technique consists in collecting all the objects that we want to delete (eg using an enhanced for loop) and after we finish iterating, we remove all found objects.第一种技术包括收集我们想要删除的所有对象(例如,使用增强的 for 循环),在我们完成迭代后,我们删除所有找到的对象。
ISBN isbn = new ISBN("0-201-63361-2"); List<Book> found = new ArrayList<Book>(); for(Book book: books){ if(book.getIsbn().equals(isbn)){ found.add(book); } } books.removeAll(found);
This is supposing that the operation you want to do is "delete".这是假设您要执行的操作是“删除”。
If you want to "add" this approach would also work, but I would assume you would iterate over a different collection to determine what elements you want to add to a second collection and then issue an addAll
method at the end.如果您想“添加”这种方法也可以,但我假设您将遍历不同的集合以确定要添加到第二个集合的元素,然后在最后发出addAll
方法。
Using ListIterator使用 ListIterator
If you are working with lists, another technique consists in using a ListIterator
which has support for removal and addition of items during the iteration itself.如果您正在使用列表,另一种技术是使用ListIterator
,它支持在迭代过程中删除和添加项目。
ListIterator<Book> iter = books.listIterator(); while(iter.hasNext()){ if(iter.next().getIsbn().equals(isbn)){ iter.remove(); } }
Again, I used the "remove" method in the example above which is what your question seemed to imply, but you may also use its add
method to add new elements during iteration.同样,我在上面的示例中使用了“删除”方法,这似乎是您的问题所暗示的,但您也可以使用它的add
方法在迭代期间添加新元素。
Using JDK >= 8使用 JDK >= 8
For those working with Java 8 or superior versions, there are a couple of other techniques you could use to take advantage of it.对于使用 Java 8 或更高版本的用户,您可以使用其他一些技术来利用它。
You could use the new removeIf
method in the Collection
base class:您可以在Collection
base class 中使用新的removeIf
方法:
ISBN other = new ISBN("0-201-63361-2"); books.removeIf(b -> b.getIsbn().equals(other));
Or use the new stream API:或者使用新的 stream API:
ISBN other = new ISBN("0-201-63361-2"); List<Book> filtered = books.stream().filter(b -> b.getIsbn().equals(other)).collect(Collectors.toList());
In this last case, to filter elements out of a collection, you reassign the original reference to the filtered collection (ie books = filtered
) or used the filtered collection to removeAll
the found elements from the original collection (ie books.removeAll(filtered)
).在最后一种情况下,要从集合中过滤元素,您将原始引用重新分配给过滤集合(即books = filtered
)或使用过滤集合从原始集合中删除所有找到的元素(即removeAll
books.removeAll(filtered)
)。
Use Sublist or Subset使用子列表或子集
There are other alternatives as well.还有其他选择。 If the list is sorted, and you want to remove consecutive elements you can create a sublist and then clear it:如果列表已排序,并且您想要删除连续的元素,您可以创建一个子列表,然后将其清除:
books.subList(0,5).clear();
Since the sublist is backed by the original list this would be an efficient way of removing this subcollection of elements.由于子列表由原始列表支持,这将是删除此元素子集合的有效方法。
Something similar could be achieved with sorted sets using NavigableSet.subSet
method, or any of the slicing methods offered there.使用NavigableSet.subSet
方法或那里提供的任何切片方法可以通过排序集实现类似的操作。
Considerations:注意事项:
What method you use might depend on what you are intending to do您使用什么方法可能取决于您打算做什么
removeAl
technique works with any Collection (Collection, List, Set, etc). collect 和removeAl
技术适用于任何 Collection(Collection、List、Set 等)。ListIterator
technique obviously only works with lists, provided that their given ListIterator
implementation offers support for add and remove operations. ListIterator
技术显然只适用于列表,前提是它们给定的ListIterator
实现提供了对添加和删除操作的支持。Iterator
approach would work with any type of collection, but it only supports remove operations. Iterator
方法适用于任何类型的集合,但它只支持删除操作。ListIterator
/ Iterator
approach the obvious advantage is not having to copy anything since we remove as we iterate.使用ListIterator
/ Iterator
方法的明显优势是不必复制任何内容,因为我们在迭代时删除。 So, this is very efficient.所以,这是非常有效的。removeAll
approach the disadvantage is that we have to iterate twice.在 collect 和removeAll
方法中,缺点是我们必须迭代两次。 First we iterate in the foor-loop looking for an object that matches our removal criteria, and once we have found it, we ask to remove it from the original collection, which would imply a second iteration work to look for this item in order to remove it.首先,我们在 for 循环中迭代,寻找符合我们删除标准的 object,一旦找到它,我们要求将其从原始集合中删除,这意味着第二次迭代工作以查找该项目以去掉它。Iterator
interface is marked as "optional" in Javadocs, which means that there could be Iterator
implementations that throw UnsupportedOperationException
if we invoke the remove method.我认为值得一提的是, Iterator
接口的 remove 方法在 Javadocs 中被标记为“可选”,这意味着如果我们调用 remove 方法,可能会有Iterator
实现抛出UnsupportedOperationException
。 As such, I'd say this approach is less safe than others if we cannot guarantee the iterator support for removal of elements.因此,如果我们不能保证迭代器支持删除元素,我会说这种方法不如其他方法安全。Old Timer Favorite (it still works):老计时器最爱(它仍然有效):
List<String> list; for(int i = list.size() - 1; i >= 0; --i) { if(list.get(i).contains("bad")) { list.remove(i); } }
Benefits:好处:
In Java 8, there is another approach.在 Java 8 中,还有另一种方法。 Collection#removeIf 收藏#removeIf
eg:例如:
List<Integer> list = new ArrayList<>(); list.add(1); list.add(2); list.add(3); list.removeIf(i -> i > 2);
Are there any reasons to prefer one approach over the other是否有任何理由偏爱一种方法而不是另一种方法
The first approach will work, but has the obvious overhead of copying the list.第一种方法可以工作,但复制列表的开销明显。
The second approach will not work because many containers don't permit modification during iteration.第二种方法不起作用,因为许多容器不允许在迭代期间进行修改。 This includes ArrayList
. 这包括ArrayList
。
If the only modification is to remove the current element, you can make the second approach work by using itr.remove()
(that is, use the iterator 's remove()
method, not the container 's).如果唯一的修改是删除当前元素,则可以使用itr.remove()
使第二种方法起作用(即,使用迭代器的remove()
方法,而不是容器的)。 This would be my preferred method for iterators that support remove()
.对于支持remove()
的迭代器,这将是我的首选方法。
Only second approach will work.只有第二种方法可行。 You can modify collection during iteration using iterator.remove()
only.您只能在迭代期间使用iterator.remove()
修改集合。 All other attempts will cause ConcurrentModificationException
.所有其他尝试将导致ConcurrentModificationException
。
You can't do the second, because even if you use the remove()
method on Iterator , you'll get an Exception thrown .你不能做第二个,因为即使你在Iterator上使用remove()
方法, 你也会得到一个 Exception throw 。
Personally, I would prefer the first for all Collection
instances, despite the additional overheard of creating the new Collection
, I find it less prone to error during edit by other developers.就个人而言,我更喜欢所有Collection
实例的第一个,尽管额外无意中听到了创建新Collection
,但我发现它在其他开发人员编辑期间不太容易出错。 On some Collection implementations, the Iterator remove()
is supported, on other it isn't.在某些 Collection 实现中,支持 Iterator remove()
,而在另一些实现中则不支持。 You can read more in the docs for Iterator .您可以在Iterator的文档中阅读更多内容。
The third alternative, is to create a new Collection
, iterate over the original, and add all the members of the first Collection
to the second Collection
that are not up for deletion.第三种选择是创建一个新的Collection
,遍历原始集合,并将第一个Collection
的所有成员添加到第二个Collection
中,这些成员不会被删除。 Depending on the size of the Collection
and the number of deletes, this could significantly save on memory, when compared to the first approach.根据Collection
的大小和删除的数量,与第一种方法相比,这可以显着节省 memory。
I would choose the second as you don't have to do a copy of the memory and the Iterator works faster.我会选择第二个,因为您不必复制 memory 并且迭代器的工作速度更快。 So you save memory and time.因此,您节省了 memory 和时间。
There is also a simple solution to "iterate" a Collection
and removing each items. 还有一个简单的解决方案可以“迭代” Collection
并删除每个项目。
List<String> list = new ArrayList<>();
//Fill the list
It simply conciste on looping until the list is empty, and on each iteration, we remove the first element with remove(0)
. 它只是简单地循环直到列表为空,然后在每次迭代中,我们使用remove(0)
删除第一个元素。
while(!list.isEmpty()){
String s = list.remove(0);
// do you thing
}
I don't believe this has any improvement compare to the Iterator
, it still required to have a mutable list but I like the simplicity of this solution. 与Iterator
相比,我认为这没有任何改进,它仍然需要具有可变列表,但是我喜欢此解决方案的简单性。
You can see this sample;你可以看到这个样本; If we think remove odd value from a list:如果我们认为从列表中删除奇数值:
public static void main(String[] args) { Predicate<Integer> isOdd = v -> v % 2 == 0; List<Integer> listArr = Arrays.asList(5, 7, 90, 11, 55, 60); listArr = listArr.stream().filter(isOdd).collect(Collectors.toList()); listArr.forEach(System.out::println); }
why not this?为什么不是这个?
for( int i = 0; i < Foo.size(); i++ ) { if( Foo.get(i).equals( some test ) ) { Foo.remove(i); } }
And if it's a map, not a list, you can use keyset()如果是 map,而不是列表,则可以使用 keyset()
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.