java singleton<t>_Java 单例列表对比:Collections::singletonList 与 List::of

本文对比了Java中创建单元素列表的两种方法:Collections::singletonList和List::of,从编程效率、代码可读性、List API支持、Null支持、内存使用和性能等方面进行了详尽分析。Java 9及更高版本引入的List::of方法在简洁性和可读性上有所提升,且返回的列表不可变,但在性能上与Collections::singletonList相近。
摘要由CSDN通过智能技术生成

09d877b11538ea84667b8cf9228a2157.png

说起单例,就像下面这样!

如何在Java中获取一个类型为 T 的单个对象,并转换为单元素List?

一种方法实例化List然后添加对象,例如ArrayList或LinkedList,但是这样做有意思吗?机智的开发人员肯定要用一行代码搞定。好消息是JavaSE提供了很多方法,只需要一行代码就能解决这个问题。

(这里不介绍所谓“双括号”实例化方法,因为即使只用一条赋值语句,实际上也是两行代码:一行实例化匿名List子类型,另一行在初始化代码块中添加对象)

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 [email protected] List} 的元素类型

* @param e1 单个元素

* @return a [email protected] List} 包含指定元素

* @throws NullPointerException 如果元素为 [email protected] null}

*

* @since 9

*/

static List of(E e1) {

return new ImmutableCollections.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.Collections$SingletonList,相比List::of返回的java.util.ImmutableCollections$List12支持更多操作。

为了比较,下面的列表中还包含了之前提到的java.util.Arrays$ArrayList和java.util.ArrayList,后面是Collectors.toList()的返回类型。Collectors.toUnmodifiableList()与List::of返回的列表类型相同

8ca49b6819d72f96ce2abd3fee216748.png

图例:

✔️ 表示支持该方法

❌表示调用此方法会抛出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.Collections$SingletonList的list-iterator不持支持set方法,但是支持sort方法。在JavaDocs中明确说明:“如果list-iterator不支持set方法,会抛出UnsupportedOperationException”。看起来java.util.Collections$SingletonList并不完全符合java.util.List规范。

ArrayList和LinkedList也有类似问题。List::sort的JavaDocs声称:“如果指定的comparator参数为null,则列表中所有元素都必须实现Comparable接口”。真的是这样吗?那为什么下面这段代码能正常工作呢?

List list = new ArrayList<>();

list.add(new Object()); // 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)

--------------------------------------------------

100077        2401848  java.util.ImmutableCollections$List12 ([email protected])

100000        2400000  java.util.Collections$SingletonList ([email protected])

[email protected]7个实例来自哪里,但是用字节数除以实例个数会发现,每个类实例恰好占用了24个字节。鉴于每个list都包含对一个item引用,这很有意义。64位JVM上的每个类占用12个字节(禁止压缩OOP),每个引用消耗8个字节,总共20个字节。当填充到接近8的倍数时,会达到24个字节。

性能

使用JMH创建一个基准测试,检测目前为止使用上述所有方法创建列表的平均时间和吞吐量:

基准模式Cnt得分误差单位

Approach.collectionsSingletonList                      thrpt        5   154.848 ± 16.030  ops/us

Approach.listOf                                        thrpt        5   147.524 ± 10.477  ops/us

Approach.arraysAsList                                  thrpt        5    90.731 ±  2.655  ops/us

Approach.streamAndCollectToList                        thrpt        5     4.481 ±  0.459  ops/us

Approach.streamAndCollectToUnmodifiableList            thrpt        5     4.235 ±  0.081  ops/us

Approach.collectionsSingletonList                       avgt        5     0.006 ±  0.001   us/op

Approach.listOf                                         avgt        5     0.007 ±  0.001   us/op

Approach.arraysAsList                                   avgt        5     0.011 ±  0.001   us/op

Approach.streamAndCollectToList                         avgt        5     0.217 ±  0.004   us/op

Approach.streamAndCollectToUnmodifiableList             avgt        5     0.241 ±  0.036   us/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,因为它使用方便、代码可读性强且不可变性更好。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值