為什么有時候要避免使用ArrayList
在工程中口渔,經常能看到類似如下代碼:
final List<String> list1 = ...;
final List<String> list2 = ...;
final List<String> results = new ArrayList<>(list1.size() + list2.size());
results.addAll(list1);
results.addAll(list2);
return results;
其功能非常簡單,將兩個list合并成一個list穿撮,而完成這個功能非常簡單搓劫,就是使用一個ArrayList,將兩個list中的元素全部加入到這個list中混巧。
但是這樣做的問題在于以下兩點:
- 時間開銷大枪向,需要將兩個list中的元素全部加入到新的list中
- 需要創(chuàng)建額外的數組空間,甚至在加入第二個list時咧党,ArrayList需要擴容
可以看到ArrayList中的代碼:
這兩段ArrayList中的代碼并沒有問題秘蛔,只不過在這樣的場景下并不是最優(yōu)的。
我們再看看我們常用的Arrays.asList()方法是如何實現的:
可以看到,Arrays.asList()方法中創(chuàng)建了一個ArrayList深员,但是此ArrayList并不是java.util.ArrayList负蠕,而是Arrays的一個靜態(tài)類部類,從截圖中的代碼可以看出倦畅,Arrays.asList()方法沒只有O(1)的時間開銷遮糖,并且沒有創(chuàng)建額外的數組。
回到前面的例子叠赐,我們怎么將兩個list合并成一個list并返回呢欲账,類似的,我們可以使用AbstractList:
很多類似的場景都非常適合使用AbstractList而不是ArrayList芭概,比如Guava中的Lists..asList()的代碼:
AbstractList
AbstractList類是專為繼承而設計的類赛不,其中提供了get和size兩個抽象方法,子類最小只需要實現這兩個方法即可罢洲,但這樣創(chuàng)建出來的List是不支持修改的踢故,我們可以看看ArrayList的源碼,其中的set/add/remove方法如下:
如果想要通過AbstractList派生出來的List支持修改惹苗,需要覆蓋這三個方法
AbstractList作為List的抽象實現殿较,其脫離了具體的數據結構,提供了不同類型的數據結構實現的List所需要的通用方法桩蓉,在AbstractList提供的默認實現中斜脂,在特定場景下可能會有性能上的不足,比如addAll方法:
如果是鏈表結構的List触机,此方法性能將非常低,時間開銷會是O(n*m)玷或,n和m是兩個list的長度儡首。而LinkedList中則選擇了覆蓋此方法,利用鏈表的尾插法重新實現了此方法從而保證了性能偏友。同樣ArrayList也重新實現了addAll方法蔬胯,前文中可以看到,通過復制數組的方式代替遍歷的方式
AbstractCollection
最后看看AbstractCollection位他,AbstractCollection提供了Collection的抽象實現氛濒,Collection接口包含Set接口和List接口,因此Collection中無法判斷出集合是有序的(List)還是無序的(Set)鹅髓,因此AbstractCollection中沒有提供迭代器的默認實現舞竿,而AbstractList中提供了迭代器的默認實現:
而在AbstractCollection接口提供的方法實現基本上都是基于迭代器的:
因此在AbstractCollection的子類中,也可能會出現AbstractCollection提供的方法有性能優(yōu)化的空間窿冯,比如把迭代器替換成arraycopy等
AbstractSet
最后看一下AbstractSet類骗奖,同樣,AbstractSet中并沒有提供迭代器的默認實現,其中只是對removeAll做了重新實現执桌,并覆蓋了hashcode和equals