文章目錄
1. 前言
操作集合是一個 Java 編程人員幾乎每天都在重復的事情。今天我們來研究一下從 Java Collection 中刪除元素的方法。我構建了一個簡單的集合,我們以此為例子來展開探索。
List<String> servers = new ArrayList<>();
servers.add("Felordcn");
servers.add("Tomcat");
servers.add("Jetty");
servers.add("Undertow");
servers.add("Resin");
2. for 循環並不一定能從集合中移除元素
讓我們使用傳統的 foreach
循環移除 F
開頭的假服務器,但是你會發現這種操作引發了 ConcurrentModificationException
異常。
// 錯誤的示范 千萬不要使用
for (String server : servers) {
if (server.startsWith("F")) {
servers.remove(server);
}
}
難道 for 循環就不能移除元素了嗎?當然不是!我們如果能確定需要被移除的元素的索引還是可以的。
// 這種方式是可行
for (int i = 0; i < servers.size(); i ) {
if (servers.get(i).startsWith("F")) {
servers.remove(i);
}
}
但是這種方式我目前只演示了 ArrayList
,其它的類型並沒有嚴格測試,留給你自己探索。
3. 迭代器 Iterator 可以刪除集合中的元素
在傳統方式中我們使用 Iterator
是可以保證刪除元素的:
Iterator<String> iterator = servers.iterator();
while (iterator.hasNext()) {
String next = iterator.next();
if (next.startsWith("F")) {
iterator.remove();
}
}
4. 遍歷刪除元素的缺點
- 我們需要遍歷集合的每一個元素並對它們進行斷言,哪怕你刪除一個元素。
- 盡管我們可以通過迭代的方式刪除特定的元素,但是操作繁瑣,根據集合類型的不同有潛在的
ConcurrentModificationException
異常。 - 根據數據結構的不同,刪除元素的時間復雜度也大大不同。比如數組結構的
ArrayList
在刪除元素的速度上不如鏈表結構的LinkedList
。
5. 新的集合元素刪除操作
Java 8 提供了新的集合操作 API 和 Stream 來幫助我們解決這個問題。我在以前的文章中已經介紹了 Java 8 Stream API,如果有興趣可以去看看。
5.1 Collection.removeIf()
新的 Collection
Api removeIf(Predicate<? super E> filter)
。該 Api 提供了一種更簡潔的使用 Predicate
(斷言)刪除元素的方法,於是我們可以更加簡潔的實現開始的需求:
servers.removeIf(s-> s.startsWith("F"));
同時根據測試,ArrayList
和 LinkedList
的性能接近。一般推薦使用這種方式進行操作。
5.2 Stream 實現移除元素
和上面所有移除操作不同的是,其實任何操作都不會改變 Stream
源,我們僅僅是使用 Stream
Api 操作數據源的副本。遵循了 數據源 -> 中間操作 -> 歸納終止
的生命周期。我們來看看使用 Stream
如何實現我們的意圖。
5.2.1 通過 filter 斷言實現
我們可以使用 Stream
的 filter
斷言。filter
斷言會把符合斷言的流元素匯集成一個新的流,然后歸納起來即可,於是我們可以這么寫:
// 跟以上不同的是 該方式中的斷言是取反的操作。
List<String> newServers = servers.stream().filter(s -> !s.startsWith("F")).collect(Collectors.toList());
這個優點上面已經說了不會影響原始數據,生成的是一個副本。缺點就是可能會有內存占用問題。
5.2.2 通過 Collectors.partitioningBy 歸納
這種方法雖然可以滿足需要但是我感覺有點投機取巧的成份。Collectors.partitioningBy()
方法本意是做二分類的。該方法會將流中符合斷言的、不符合斷言的元素分別歸納到兩個 key
分別為 true
和 false
的 Map
中,我們可以歸類得到符合和不符合的元素集。實現如下:
Map<Boolean, List<String>> f = servers.stream().collect(Collectors.partitioningBy(s -> !s.startsWith("F")));
List<String> trues = f.get(Boolean.TRUE);
System.out.println("不以 F 開頭的: " trues);
List<String> falses = f.get(Boolean.FALSE);
System.out.println("以 F 開頭的: " falses);
一般該方式不推薦在此場景使用,並不符合該 Api 的設計意圖。
6. 總結
今天我們研究了一些從 Collections
中刪除元素的方法 及其注意事項。不知道你有沒有其它的實現方式,不妨通過公眾號:Felordcn 告訴我。
關注公眾號:Felordcn獲取更多資訊