如何解决迭代时从集合中删除元素
让我举几个例子和一些替代方案来避免ConcurrentModificationException
.
假设我们有以下书籍集合
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")));
第一种技术包括收集我们想要删除的所有对象(例如,使用增强的 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);
这是假设您要执行的操作是“删除”。
如果您想“添加”这种方法也可以,但我假设您将遍历不同的集合以确定要添加到第二个集合的元素,然后addAll
在最后发出一个方法。
如果您正在使用列表,另一种技术是使用ListIterator
支持在迭代过程中删除和添加项目的 a。
ListIterator<Book> iter = books.listIterator();
while(iter.hasNext()){
if(iter.next().getIsbn().equals(isbn)){
iter.remove();
}
}
同样,我在上面的示例中使用了“删除”方法,这似乎是您的问题所暗示的,但您也可以使用它的add
方法在迭代期间添加新元素。
对于那些使用 Java 8 或更高版本的人,您可以使用其他一些技术来利用它。
您可以在基类中使用新removeIf
方法:Collection
ISBN other = new ISBN("0-201-63361-2");
books.removeIf(b -> b.getIsbn().equals(other));
或者使用新的流 API:
ISBN other = new ISBN("0-201-63361-2");
List<Book> filtered = books.stream()
.filter(b -> b.getIsbn().equals(other))
.collect(Collectors.toList());
在最后一种情况下,要从集合中过滤元素,您可以将原始引用重新分配给过滤的集合(ie books =
filtered
),或者将过滤的集合用于removeAll
从原始集合(ie books.removeAll(filtered)
)中找到的元素。
还有其他选择。如果列表已排序,并且您想要删除连续的元素,您可以创建一个子列表,然后将其清除:
books.subList(0,5).clear();
由于子列表由原始列表支持,这将是删除此元素子集合的有效方法。
NavigableSet.subSet
使用方法的排序集或那里提供的任何切片方法都可以实现类似的效果。
您使用什么方法可能取决于您打算做什么
- 收集和
removeAl
技术适用于任何集合(集合、列表、集合等)。 - 该
ListIterator
技术显然只适用于列表,前提是它们的给定ListIterator
实现支持添加和删除操作。 - 该
Iterator
方法适用于任何类型的集合,但它仅支持删除操作。 - 使用
ListIterator
/Iterator
方法的明显优势是不必复制任何内容,因为我们在迭代时删除。所以,这是非常有效的。 - JDK 8 流示例实际上并没有删除任何东西,而是寻找所需的元素,然后我们用新的元素替换原始的收集引用,并让旧的收集引用。因此,我们只对集合进行一次迭代,这样会很有效。
- 在收集和
removeAll
方法中,缺点是我们必须迭代两次。首先,我们在 foo 循环中迭代,寻找一个符合我们移除标准的对象,一旦我们找到它,我们就要求将它从原始集合中移除,这意味着第二次迭代工作来寻找这个项目,以便去掉它。 - 我认为值得一提的是,
Iterator
接口的 remove 方法在 Javadocs 中被标记为“可选”,这意味着如果我们调用 remove 方法,可能会有一些Iterator
实现抛出。UnsupportedOperationException
因此,如果我们不能保证迭代器支持删除元素,我会说这种方法不如其他方法安全。
解决方法
AFAIK,有两种方法:
- 遍历集合的副本
- 使用实际集合的迭代器
例如,
List<Foo> fooListCopy = new ArrayList<Foo>(fooList);
for(Foo foo : fooListCopy){
// modify actual fooList
}
和
Iterator<Foo> itr = fooList.iterator();
while(itr.hasNext()){
// modify actual fooList using itr.remove()
}
有什么理由更喜欢一种方法而不是另一种(例如,出于可读性的简单原因更喜欢第一种方法)?
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。