簡體   English   中英

迭代時從集合中刪除元素

[英]Remove elements from collection while iterating

AFAIK,有兩種方法:

  1. 遍歷集合的副本
  2. 使用實際集合的迭代器

例如,

 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方法或那里提供的任何切片方法可以通過排序集實現類似的操作。

注意事項:

您使用什么方法可能取決於您打算做什么

  • collect 和removeAl技術適用於任何 Collection(Collection、List、Set 等)。
  • ListIterator技術顯然只適用於列表,前提是它們給定的ListIterator實現提供了對添加和刪除操作的支持。
  • Iterator方法適用於任何類型的集合,但它只支持刪除操作。
  • 使用ListIterator / Iterator方法的明顯優勢是不必復制任何內容,因為我們在迭代時刪除。 所以,這是非常有效的。
  • JDK 8 流示例實際上並沒有刪除任何內容,而是查找所需的元素,然后我們用新的元素替換原始收集引用,並讓舊的收集引用進行垃圾收集。 因此,我們只對集合進行一次迭代,這樣會很有效。
  • 在 collect 和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); } }

好處:

  1. 它只遍歷列表一次
  2. 沒有創建額外的對象或其他不需要的復雜性
  3. 嘗試使用已刪除項目的索引沒有問題,因為...好吧,考慮一下

在 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM