[英]Remove elements from collection while iterating
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() }
有什么理由更喜歡一種方法而不是另一種(例如,出於可讀性的簡單原因更喜歡第一種方法)?
讓我舉幾個例子來避免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
如果您正在使用列表,另一種技術是使用ListIterator
,它支持在迭代過程中刪除和添加項目。
ListIterator<Book> iter = books.listIterator(); while(iter.hasNext()){ if(iter.next().getIsbn().equals(isbn)){ iter.remove(); } }
同樣,我在上面的示例中使用了“刪除”方法,這似乎是您的問題所暗示的,但您也可以使用它的add
方法在迭代期間添加新元素。
使用 JDK >= 8
對於使用 Java 8 或更高版本的用戶,您可以使用其他一些技術來利用它。
您可以在Collection
base class 中使用新的removeIf
方法:
ISBN other = new ISBN("0-201-63361-2"); books.removeIf(b -> b.getIsbn().equals(other));
或者使用新的 stream API:
ISBN other = new ISBN("0-201-63361-2"); List<Book> filtered = books.stream().filter(b -> b.getIsbn().equals(other)).collect(Collectors.toList());
在最后一種情況下,要從集合中過濾元素,您將原始引用重新分配給過濾集合(即books = filtered
)或使用過濾集合從原始集合中刪除所有找到的元素(即removeAll
books.removeAll(filtered)
)。
使用子列表或子集
還有其他選擇。 如果列表已排序,並且您想要刪除連續的元素,您可以創建一個子列表,然后將其清除:
books.subList(0,5).clear();
由於子列表由原始列表支持,這將是刪除此元素子集合的有效方法。
使用NavigableSet.subSet
方法或那里提供的任何切片方法可以通過排序集實現類似的操作。
注意事項:
您使用什么方法可能取決於您打算做什么
removeAl
技術適用於任何 Collection(Collection、List、Set 等)。ListIterator
技術顯然只適用於列表,前提是它們給定的ListIterator
實現提供了對添加和刪除操作的支持。Iterator
方法適用於任何類型的集合,但它只支持刪除操作。ListIterator
/ Iterator
方法的明顯優勢是不必復制任何內容,因為我們在迭代時刪除。 所以,這是非常有效的。removeAll
方法中,缺點是我們必須迭代兩次。 首先,我們在 for 循環中迭代,尋找符合我們刪除標准的 object,一旦找到它,我們要求將其從原始集合中刪除,這意味着第二次迭代工作以查找該項目以去掉它。Iterator
接口的 remove 方法在 Javadocs 中被標記為“可選”,這意味着如果我們調用 remove 方法,可能會有Iterator
實現拋出UnsupportedOperationException
。 因此,如果我們不能保證迭代器支持刪除元素,我會說這種方法不如其他方法安全。老計時器最愛(它仍然有效):
List<String> list; for(int i = list.size() - 1; i >= 0; --i) { if(list.get(i).contains("bad")) { list.remove(i); } }
好處:
在 Java 8 中,還有另一種方法。 收藏#removeIf
例如:
List<Integer> list = new ArrayList<>(); list.add(1); list.add(2); list.add(3); list.removeIf(i -> i > 2);
是否有任何理由偏愛一種方法而不是另一種方法
第一種方法可以工作,但復制列表的開銷明顯。
第二種方法不起作用,因為許多容器不允許在迭代期間進行修改。 這包括ArrayList
。
如果唯一的修改是刪除當前元素,則可以使用itr.remove()
使第二種方法起作用(即,使用迭代器的remove()
方法,而不是容器的)。 對於支持remove()
的迭代器,這將是我的首選方法。
只有第二種方法可行。 您只能在迭代期間使用iterator.remove()
修改集合。 所有其他嘗試將導致ConcurrentModificationException
。
你不能做第二個,因為即使你在Iterator上使用remove()
方法, 你也會得到一個 Exception throw 。
就個人而言,我更喜歡所有Collection
實例的第一個,盡管額外無意中聽到了創建新Collection
,但我發現它在其他開發人員編輯期間不太容易出錯。 在某些 Collection 實現中,支持 Iterator remove()
,而在另一些實現中則不支持。 您可以在Iterator的文檔中閱讀更多內容。
第三種選擇是創建一個新的Collection
,遍歷原始集合,並將第一個Collection
的所有成員添加到第二個Collection
中,這些成員不會被刪除。 根據Collection
的大小和刪除的數量,與第一種方法相比,這可以顯着節省 memory。
我會選擇第二個,因為您不必復制 memory 並且迭代器的工作速度更快。 因此,您節省了 memory 和時間。
還有一個簡單的解決方案可以“迭代” Collection
並刪除每個項目。
List<String> list = new ArrayList<>();
//Fill the list
它只是簡單地循環直到列表為空,然后在每次迭代中,我們使用remove(0)
刪除第一個元素。
while(!list.isEmpty()){
String s = list.remove(0);
// do you thing
}
與Iterator
相比,我認為這沒有任何改進,它仍然需要具有可變列表,但是我喜歡此解決方案的簡單性。
你可以看到這個樣本; 如果我們認為從列表中刪除奇數值:
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); }
為什么不是這個?
for( int i = 0; i < Foo.size(); i++ ) { if( Foo.get(i).equals( some test ) ) { Foo.remove(i); } }
如果是 map,而不是列表,則可以使用 keyset()
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.