为什么将枚举转换为ArrayList而不转换为java.utils中的List?


74

程序包中的Collections.list()方法java.utils返回ArrayList<T>而不是的充分理由List<T>吗?

显然,anArrayList是a List,但我的印象是返回接口类型而不是实现类型通常是一种好习惯。


在Javadoc中:“返回数组列表[...] ”。他们为什么决定锁定API以返回,这ArrayList是众所周知的,即使查看的源代码,我们也无法回答这个问题Collections
Laf 2015年

可能是因为必须保持枚举的顺序?它在您的问题的链接中提到,返回一个数组列表
Amit.rk3

5
@ Amit.rk3无论您选择哪种实现,列表始终保持顺序。
Jib'z 2015年

@ Amit.rk3 AList保留添加元素的顺序,或者至少是由List接口定义的常规协定:“有序集合(也称为序列) ”。
Laf 2015年

@ Amit.rk3还请注意,Enumeration(以及它所代表的枚举,小写的“ e”)不同于enum。他们有一个名字,但没有别的。
yshavit 2015年

Answers:


54

免责声明:我不是JDK作者。

我同意将自己的代码编写到接口是正确的,但是如果要将可变的集合返回给第三方,让第三方知道List它们正在返回哪种类型非常重要。

LinkedList并且ArrayList有很大的不同,在性能方面,进行各种操作。例如,去除的第一元件ArrayListO(n),但除去所述第一元件的LinkedListO(1)

通过完全指定返回类型,JDK作者可以用明确的代码传达有关他们将给您什么样的对象的额外信息,因此您可以编写代码以正确使用此方法。如果确实需要一个LinkedList,那么您必须在此处指定一个。

最后,在实现上对接口进行编码的主要原因是,如果您认为实现会改变。JDK作者可能认为他们永远不会改变这种方法。它永远不会返回aLinkedList或a Collections.UnmodifiableList。但是,在大多数情况下,您可能仍会这样做:

List<T> list = Collections.list(enumeration);

19
似乎这是一种API设计的Postel定律:输入内容一般,输出内容特定。
Tianxiang Xiong

2
有一天,他们将添加一个toLinkedList方法,然后listArrayList版本的名称看起来很愚蠢。
user253751'9

1
我认为这里的重要信息是列表是否可变。例如,接口的哪些方法会引发UnsupportedOperationException,而哪些则不会。
keuleJ

32

返回时List,您将把程序提升为接口,这是一个很好的做法。但是,这种方法有其局限性。例如,您不能使用某些为接口定义ArrayList且不存在的方法List-有关详细信息,请参见此答案

我引用了Java™教程中的API设计

......它的优良返回的对象的任何类型的那个工具扩展集合接口之一。这可以是接口之一,也可以是扩展或实现这些接口之一的专用类型。

从某种意义上说,返回值应该具有与输入参数相反的行为:最好返回最具体的适用集合接口,而不是最通用的接口。例如,如果您确定将始终返回a SortedMap,则应为相关方法提供返回类型SortedMap而不是MapSortedMap实例的构建比普通Map实例更耗时,并且功能也更强大。既然您的模块已经投入时间来构建SortedMap,那么让用户访问其增强的功能非常有意义。此外,用户将能够将返回的对象传递给需要a的方法SortedMap以及接受any的方法Map

由于ArrayList本质上是一个数组,当我需要一个“集合数组”时,它们是我的首选。因此,如果要将枚举转换为列表,则选择数组列表。

在其他情况下,仍然可以这样写:

List<T> list = Collections.list(e);

4
关于“最具体的适用集合接口而不是最通用的”,这是有道理的,但是ArrayList不是接口。在这种情况下,列表似乎是最特定的接口。
Tianxiang Xiong

2
在a的情况下SortedMap,它为normal添加了功能Map,因此我认为将其指定为返回类型是有意义的。但是,ArrayList不会给添加任何内容List,并且可以与进行交换,LinkedList您将看不到任何不同(在功能方面而不是性能方面)。不过,我认为您的参考非常有趣。+1。
Laf 2015年

1
@Laf选择LinkedList或时实际上要考虑很多事情ArrayList。喜欢把自己的时间复杂度..
Maroun

5
要强调这一点:这应该是设计决策,而不是“巧合”。如果您将数据内部存储在中SortedMap,则不应“因为它一个”将其返回。您应该确定呼叫者是否应该知道这是一个。而且您应该知道,一旦返回a ,就永远无法将其归纳为一个普通的东西-而总是有另一个方向(变得更加具体,并返回原来的a,仅返回了a )。SortedMapSortedMapMapSortedMapMap
Marco13年

6

返回新创建的可变对象的“专有所有权”的函数通常应该是最具体的实用类型。那些返回不可变对象的对象,特别是如果它们可能是共享的,则应该经常返回不太具体的类型。

区别的原因是,在前一种情况下,对象将始终能够产生指定类型的新对象,并且由于接收者将拥有该对象,并且无法告知接收者可能希望执行的操作,因此返回对象的代码通常无法知道任何其他接口实现是否可以满足接收者的需求。

在后一种情况下,对象是不可变的这一事实意味着该函数可能能够识别出替代类型,该类型可以在给定其确切内容的情况下完成更复杂的类型可以执行的所有操作。例如,Immutable2dMatrix接口可以由一个ImmutableArrayBacked2dMatrix类和一个ImmutableDiagonal2dMatrix类来实现。如果主对角线上的所有元素都为零,或者不是,那么应该返回一个平方的函数Immutable2dMatrix可以决定返回一个ImmutableDiagonalMatrix实例ImmutableArrayBackedMatrix。前一种将占用更少的存储空间,但是接收者不必在意它们之间的区别。

返回Immutable2dMatrix而不是具体ImmutableArrayBackedMatrix允许代码根据数组包含的内容选择返回类型。这也意味着,如果应该返回数组的代码恰好持有该数组的适当实现,Immutable2dMatrix则可以简单地返回该数组,而不必构造新的实例。当处理不可变对象时,这两个因素都是主要的“胜利”。

但是,当使用可变对象时,这两个因素都不起作用。可变数组在生成时在主对角线上可能没有任何元素的事实并不意味着它永远不会有任何这样的元素。因此,虽然anImmutableDiagonalMatrix实际上是an的子类型Immutable2dMatrix,但是aMutableDiagonalMatrix不是a的子类型Mutable2dMatrix,因为后者可以接受主对角线的存储,而前者则不能。此外,虽然不可变对象经常可以并且应该被共享,但是可变对象通常不能。一个要求使用某些内容初始化的新可变集合的函数将需要创建一个新集合,无论其后备存储是否与请求的类型匹配。


1

有一个的对象上调用方法VAR的接口,而不是直接开销。

该开销通常不超过1或2个处理器指令。如果JIT知道该方法是最终方法,则调用方法的开销甚至更低。对于您自己和我大多数的代码来说,这是无法测量的,但是对于java.utils中的低级方法,可能会在某些有问题的代码中使用。

同样在其他答案中已经指出,返回的对象的具体类型(即使隐藏在接口后面)也会影响使用该对象的代码的性能。这种性能上的变化可能非常大,以至于调用软件无法正常工作。

显然,作者java.utils没有办法知道所有调用Collections.list()的软件会对结果产生什么影响,并且如果他们更改了Collections.list()的植入方式,也没有办法重新测试该软件。 因此,即使类型系统允许,它们也不会更改Collections.list()的植入以返回其他类型的List!

在编写自己的软件时,(希望)您进行了涵盖所有代码的自动化测试,并且很好地了解了代码之间的相互关系,包括知道性能存在问题。在软件设计不断变化的同时,能够更改方法而不必更改调用方的价值很大。

因此,这两组权衡是非常不同的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.