Java List单例表: Collections::singletonList VS List::of
Posted 沛沛老爹
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java List单例表: Collections::singletonList VS List::of相关的知识,希望对你有一定的参考价值。
在Java中,怎样将一个类型T的单个对象,转换为一单例的List<T>?
一种最简单的方法,创建并实例化为一个ArraList或者LinkedList的对象。然后把它Add进去。
但是,你会发现这种做法缺少了编码的乐趣,让人感觉没有追求。像我们这种有悟性的研发,就应该想着怎么用一行代码来搞定它。好消息是JavaSE提供了多个一行代码来解决这个问题的方法。
在这里,我们不讨论那种匿名函数的双括号的实现方式。因为看起来是使用了一行代码来创建和添加单个对象。但是它实际上是两行代码:一行来实例化List的子类型,一行在初始块代码中添加单个对象。
Java 8 以及更早版本实现方式
从Java 1.3开始,就提供下面的静态工厂方法:
Collections::singletonList
List<Object> list = Collections.singletonList(item);
想着少写些代码的码农们可以会想到使用这个工厂方法Arrays::asList,这个方法在Java1.2版本就已提供了实现
List<Object> list = Arrays.asList(item);
但是说实话,这个方法并不是很合适。asList方法只接收varargs参数。
asList具体代码
/**
* Returns a fixed-size list backed by the specified array. (Changes to
* the returned list "write through" to the array.) This method acts
* as bridge between array-based and collection-based APIs, in
* combination with @link Collection#toArray. The returned list is
* serializable and implements @link RandomAccess.
*
* <p>This method also provides a convenient way to create a fixed-size
* list initialized to contain several elements:
* <pre>
* List<String> stooges = Arrays.asList("Larry", "Moe", "Curly");
* </pre>
*
* @param <T> the class of the objects in the array
* @param a the array by which the list will be backed
* @return a list view of the specified array
*/
@SafeVarargs
@SuppressWarnings("varargs")
public static <T> List<T> asList(T... a)
return new ArrayList<>(a);
这就意味着在创建list之前,需要先把参数item包装成数组形式。被创建的类型是ArraryList,实际得到的类型可能不是java.util.ArrayList,而是java.util.Arrays$ArrayList私有类,两者有以下区别:
- 没有实现Cloneable(不是很明显)。
- list是通过asList方法来传递数组。java.util.ArrayList类创建和管理自己内部的数组。
- It does not support many operations of the
java.util.List
interface, particularly the mutation methods. (See the table below for details.) - 很多java.util.List接口中的操作不支持,尤其是变异的方法(详细看后续)
Java8的Stream API提供了更多创建单例List的方法,虽然看起来没有那么直观。
List<Object> list = Stream.of(item).collect(Collectors.toList());
List<Object> immutableList = Stream.of(item).collect(Collectors.toUnmodifiableList());
不论使用哪种种单例List的生成的集合的方式:都是创建一个Stream或Collector列表,再往里面添加List对象。这看起来都不是个好办法,对于我们来讲只需要关心创建这个事情就OK了。
对于Java8或者更早的版本,Collections::sigletionList 是最好的创建单例list的方法了。
Java8以后的版本来讲,它还是最优解吗?
Java 9 及后续版本
在Java 9中增加了一个很棒的API:List::of,它可以接收一个或者多个参数,返回结果是包含当前参数的List对象。
乍一看好像和Arrays::asList差不多。
但是仔细查看后,你会发现List::of方法支持多参数的重载。
方法说明信息链接地址: one that is relevant to this particular discussion:
/**
* Returns an unmodifiable list containing one element.
*
* See <a href="#unmodifiable">Unmodifiable Lists</a> for details.
*
* @param <E> the @code List's element type
* @param e1 the single element
* @return a @code List containing the specified element
* @throws NullPointerException if the element is @code null
*
* @since 9
*/
static <E> List<E> of(E e1)
return new ImmutableCollections.List12<>(e1);
为了避免使用varargs数组,List::of支持从1到10个固定数量的命名参数的方式,就像你想的那样,其它的List::of接收varargs参数的方法,都是用来处理那种超过11个或以上的参数的。
List::of与Collections::singletonList相比,哪个更厉害一些呢?
Collections::singletonList List::of比较
我们可以从多个不同的角度来比较这两个工厂方法。
- 编程效率:方法各自适用什么场景?
- 代码可读性:方法各自对代码可读性有什么影响?
- List API支持:方法各自返回的List实例支持哪些操作?
- Null支持:哪个方法支持null?
- 内存使用情况:方法返回的List实例消耗多少内存?
- 性能:每种方法创建List的效率怎样?
编程效率
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返回的列表类型相同
SINGLETONLIST | LIST::OF | ARRAYS::ASLIST | JAVA.UTIL.ARRAYLIST | |
---|---|---|---|---|
add | ❌ | ❌ | ❌ | ✔️ |
addAll | ❌ | ❌ | ❌ | ✔️ |
clear | ❌ | ❌ | ❌ | ✔️ |
remove | ❌ | ❌ | ❌ | ✔️ |
removeAll | ❗️ | ❌ | ❗️ | ✔️ |
retainAll | ❗️ | ❌ | ❗️ | ✔️ |
replaceAll | ❌ | ❌ | ✔️ | ✔️ |
set | ❌ | ❌ | ✔️ | ✔️ |
sort | ✔️ | ❌ | ✔️ | ✔️ |
remove on iterator | ❌ | ❌ | ❌ | ✔️ |
set on list-iterator | ❌ | ❌ | ✔️ | ✔️ |
图例:
- ✔️ 表示支持该方法
- ❌ 表示调用该方法将会抛出
UnsupportedOperationException
- ❗️ 表示仅在方法参数不会引起mutation的情况下才支持该方法,例如Collections.singletonList("foo").retainAll("foo")没问题,但Collections.singletonList("foo").retainAll("bar")会抛出UnsupportedOperationException
从不变性的角度来考虑的话,List::of方法的 ImmutableCollections.List12
算是最强的; 不论传入的参数如何,每个方法都会抛出UnsupportedOperationException。
Collections::singletonList尽管允许调用一些“改变属性”的方法,但最终结果还是不可变的。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<Object> list = new ArrayList<>();
// java.lang.Object 未实现 Comparable
list.add(new Object());
// 不抛出 java.lang.ClassCastException异常
list.sort(null);
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 (java.base@12.0.2)
100000 2400000 java.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创建一个基准测试,检测目前为止使用上述所有方法创建列表的平均时间和吞吐量:
Benchmark Mode Cnt Score Error Units
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,因为它使用方便、代码可读性强且不可变性更好。
以上是关于Java List单例表: Collections::singletonList VS List::of的主要内容,如果未能解决你的问题,请参考以下文章
单列(写了池子pool)用list实现的方法, 与伪单例(写了池子zidianpool),用字典实现的方法,可以存入不同,i名字的物体
java Collections.sort()实现List排序的默认方法和自定义方法
java Collections.sort()实现List排序的默认方法和自定义方法