如何選擇Arrays.asList()、Collections.singletonList()?

Arrays.asList()、Collections.singletonList()

Arrays.asList(strArray)返回值是仍然是一個可變的集合,但是返回值是其內部類,不具有add方法,可以通過set方法進行增加值,默認長度是10
Collections.singletonList()返回的是不可變的集合,但是這個長度的集合只有1,可以減少內存空間。但是返回的值依然是Collections的內部實現類,同樣沒有add的方法,調用add,set方法會報錯
java.lang.UnsupportedOperationException //調用add()都會報錯

如何將一個元素優雅的轉換成一個集合
List authcChannels = Stream.of(K).collect(Collectors.toList());

Java 8及早期版本

Java 1.3開始就提供下面的靜態工廠方法:

Collections::singletonList.

List list= Collections.singletonList(item);

希望進一步節省代碼的開發者能會想到Arrays::asList工廠方法。這個方法從Java 1.2起就一直存在。

List list= Arrays.asList(item);

然而這種方案並不可取。asList方法只接收varargs參數,這意味着item參數創建列表之前就已經包裝爲數組。用這種方法創建的List類型是ArrayList,這並不奇怪。但實際得到的類型可能不是java.util.ArrayList,而是java.util.Arrays$ArrayList私有類,兩者有以下區別:

沒有實現Cloneable接口(不是很明顯)。
列表底層實際上是傳遞給asList方法的數組。java.util.ArrayList會創建內部數組並對其管理。
不支持java.util.List接口定義的許多方法,尤其是mutation方法。(詳細信息參見下表)
Java 8中Stream API提供了更多創建單項列表的方法,儘管使用的方法更不直觀:

List list= Stream.of(item).collect(Collectors.toList);

List immutableList = Stream.of(item).collect(Collectors.toUnmodifiableList);

無論採用哪種collector,這種方法都不可取。因爲除了創建List之外還創建了一個StreamCollector和Collector。只有前者纔是我們真正唯一關心的事情。

對於Java 8及早期版本Collections::singletonList是單行代碼中創建單元列表最好的辦法。

對於Java 8以後的Java版本還是最佳選擇嗎?

Java 9及更高版本

Java 9中增加了一個很棒的API,List::of。接受一個或多個參數,返回一個包含這些參數的List。乍一看似乎和Arrays::asList沒什麼區別。但是仔細檢查後您會注意到,List::of方法支持不同數量的參數,包括下面討論中的元素:

/**

  • 返回包含一個元素的不可變列表

  • 更多信息參見 Unmodifiable Lists

  • @param {@code List} 的元素類型

  • @param e1 單個元素

  • @return a {@code List} 包含指定元素

  • @throws NullPointerException 如果元素爲 {@code null}

  • @since 9

*/

static List of(E e1) {

returnnewImmutableCollections.List12<>(e1);

}

List::of支持從一到十個固定數量的命名參數,所有這些都是爲了避免使用varargs數組。如您所料,餘下的List::of方法接受varargs參數,處理極少數情況下列表中包含11個甚至個更多item。

List::of與Collections::singletonList相比,哪個更勝一籌?

Collections::singletonList vs. List::of

可以從多個角度比較這兩個工廠。

編程效率:方法各自適用什麼場景?
代碼可讀性:使用方法對代碼可讀性有什麼影響?
List API支持:方法返回的List實例支持哪些操作?
Null支持:哪個方法支持null?
內存使用:方法返回的List實例消耗多少內存?
性能:每種方法創建List的效率如何?
編程效率

與Collections.singletonList相比,List.of代碼更少。導入java.util.Collections不需要輸入(或者用IDE快捷方式)。由於需要處理List返回值,通常已經導入了java.util.List。

此外,如果需要增加item,只要向List::of中加入參數,無需改用其他方法。

代碼可讀性

儘管Collections::singletonList明確表明返回的列表僅包含一個item,但List.of(item)的返回值也很清楚:“返回包含此item的一個列表。”在我看來,這樣讀起來很自然。

實際上,結果是一個list這個事實比列表中只有一個item更重要。List::of突出了這個信息,而Collections::singletonList一直看到最後四個字母才解除了我們的疑惑。

List API支持

每個工廠方法返回的java.util.List實現各有不同,並且支持的API方法之間存在細微差異。Collections::singletonList返回java.util.CollectionsSingletonListList::ofjava.util.ImmutableCollectionsSingletonList,相比List::of返回的java.util.ImmutableCollectionsList12支持更多操作。

爲了比較,下面的列表中還包含了之前提到的java.util.Arrays$ArrayList和java.util.ArrayList,後面是Collectors.toList的返回類型。Collectors.toUnmodifiableList與List::of返回的列表類型相同

在這裏插入圖片描述

圖例:

✔️ 表示支持該方法
❌表示調用此方法會拋出UnsupportedOperationException;
❗️表示僅在方法參數不會引起mutation的情況下才支持該方法,例如Collections.singletonList(“foo”).retainAll(“foo”)沒問題,但Collections.singletonList(“foo”).retainAll(“bar”)會拋出UnsupportedOperationException
從不變性角度考慮List::of的ImmutableCollections.List12最強;不論傳入的參數如何,每個方法都會拋出UnsupportedOperationException。

Collections::singletonList儘管允許調用一些“mutator”方法,但最終結果還是不可變的。

Arrays::asList 返回值類型是可變的;可以修改返回值(同時會更改傳給工廠方法的數組值),但不能添加或刪除item調整大小。

有趣的是,java.util.CollectionsSingletonListlistiteratorsetsortJavaDocslistiteratorsetUnsupportedOperationExceptionjava.util.CollectionsSingletonList的list-iterator不持支持set方法,但是支持sort方法。在JavaDocs中明確說明:“如果list-iterator不支持set方法,會拋出UnsupportedOperationException”。看起來java.util.CollectionsSingletonList並不完全符合java.util.List規範。

ArrayList和LinkedList也有類似問題。List::sort的JavaDocs聲稱:“如果指定的comparator參數爲null,則列表中所有元素都必須實現Comparable接口”。真的是這樣嗎?那爲什麼下面這段代碼能正常工作呢?

List list= newArrayList<>;

list.add( newObject); // java.lang.Object 並沒有實現 Comparable 接口

list.sort(null); // 沒有拋出 java.lang.ClassCastException

Null支持

如果出於某些奇怪的原因打算創建包含一個null元素的單元列表,那麼不能使用List:of,NullPointerException會映入你眼簾(沒錯,NullPointerException可以用作動詞)。Array::asList和基於Stream的方法也是如此。

Collections::singletonList支持創建null列表。

內存使用

使用jcmd爲一個簡單程序生成了GC.class_histogram。這個該程序使用Collections::singletonList與List::of各創建了十萬個列表。

#instances #bytes class name (module)


1000772401848java.util.ImmutableCollections$List12 (java.base@ 12.0.2)

1000002400000java.util.Collections$SingletonList (java.base@ 12.0.2)

我不太確定java.util.ImmutableCollections$List12多出來的77個實例來自哪裏,但是用字節數除以實例個數會發現,每個類實例恰好佔用了24個字節。鑑於每個list都包含對一個item引用,這很有意義。64位JVM上的每個類佔用12個字節(禁止壓縮OOP),每個引用消耗8個字節,總共20個字節。當填充到接近8的倍數時,會達到24個字節。

性能

使用JMH創建一個基準測試,檢測目前爲止使用上述所有方法創建列表的平均時間和吞吐量:

基準模式Cnt得分誤差單位

Approach.collectionsSingletonList thrpt 5154.848± 16.030ops/us

Approach.listOf thrpt 5147.524± 10.477ops/us

Approach.arraysAsList thrpt 590.731± 2.655ops/us

Approach.streamAndCollectToList thrpt 54.481± 0.459ops/us

Approach.streamAndCollectToUnmodifiableList thrpt 54.235± 0.081ops/us

Approach.collectionsSingletonList avgt 50.006± 0.001us/op

Approach.listOf avgt 50.007± 0.001us/op

Approach.arraysAsList avgt 50.011± 0.001us/op

Approach.streamAndCollectToList avgt 50.217± 0.004us/op

Approach.streamAndCollectToUnmodifiableList avgt 50.241± 0.036us/op

根據這些數據,Collections::singletonList的吞吐量比List::of略高且平均執行速度略快,但是性能基本相同。

下一個Arrays::asList,速度大約是它的兩倍,吞吐量是它的60%。相比之下,Stream API提供的兩種方法測試結果非常糟糕。

爲什麼Collections::singletonList的性能要比List::of的性能略好一些?我猜測唯一的可能是java.util.ImmutableCollections.List12調用了Objects::requireNonNull強制保證“不允許傳入null”。就像前面提到的那樣,java.util.Collections$SingletonList支持null(無論好壞),因此不對constructor的參數進行任何檢查。

總結

Collections::singletonList和List::of都是創建單元列表的絕佳選擇。如果使用的Java版本支持這兩種方法(Java 9及更高版本),我建議使用List:of,因爲它使用方便、代碼可讀性強且不可變性更好

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章