java.utils包中的Collections.list()
方法返回ArrayList<T>
而不是List<T>
,这是否有充分的理由呢?
显然,ArrayList
是一个List
,但我认为通常最好返回接口类型而不是实现类型。
java.utils包中的Collections.list()
方法返回ArrayList<T>
而不是List<T>
,这是否有充分的理由呢?
显然,ArrayList
是一个List
,但我认为通常最好返回接口类型而不是实现类型。
免责声明:我不是JDK的作者。
我同意编写你自己的代码来实现接口的做法是正确的,但是如果你要向第三方返回一个可变的集合,就很重要让第三方知道他们得到了什么样的List
。
LinkedList
和ArrayList
在性能上有很大的差异,对于各种操作而言都如此。例如,从ArrayList
中删除第一个元素是O(n)
,而从LinkedList
中删除第一个元素是O(1)
。
通过完全指定返回类型,JDK作者正在用明确的代码传递额外的信息,告诉你他们正在返回什么样的对象,这样你就可以编写正确使用该方法的代码。如果你真的需要一个LinkedList
,那么你知道你必须在这里指定一个。
最后,选择面向接口而不是具体实现的主要原因是如果你认为实现会变化。JDK作者可能认为他们永远不会更改此方法; 它永远不会返回LinkedList
或Collections.UnmodifiableList
。不过,在大多数情况下,你可能仍然会这样做:
List<T> list = Collections.list(enumeration);
toLinkedList
方法,然后 ArrayList 版本的名称 list
看起来就很傻了。 - user253751List
时,您将会使用“面向接口编程”的方法,这是一种非常好的实践。但是,这种方法也有其限制。例如,您不能使用一些仅适用于 ArrayList
而在 List
接口中不存在的方法 - 详情请参见 this answer。由于 ArrayList
本质上是一个数组,当我需要一个“集合-数组”时,它们是我的首选。因此,如果我想将枚举转换为列表,我的选择将是一个数组列表。
在任何其他情况下,仍然可以编写以下内容:
List<T> list = Collections.list(e);
SortedMap
的情况下,它为普通的Map
添加了功能,因此我认为将其指定为返回类型是有意义的。然而,ArrayList
对List
没有任何增加,可以与LinkedList
交换,你不会看到任何不同(在功能上而非性能上)。尽管如此,我认为您的参考非常有趣。+1。 - LafSortedMap
,你不应该因为它“就是”一个SortedMap
就直接返回它。你应该决定调用者是否需要知道这是一个SortedMap
。同时要注意,一旦你返回了一个SortedMap
,你将永远无法将其泛化回普通的Map
——而另一个方向(变得更加具体,并返回一个SortedMap
,其中最初只返回了一个Map
)则始终是可能的。 - Marco13返回“独占所有权”的新创建的可变对象的函数应该尽可能具体;返回不可变对象的函数,特别是如果它们可能被共享,应该返回较不具体的类型。
之所以要区分这两种情况,是因为在前一种情况下,对象将始终能够生成指定类型的新对象,并且由于接收方将拥有该对象并且无法确定接收方可能希望执行什么操作,因此通常没有办法让返回对象的代码知道任何替代接口实现是否能够满足接收方的需求。
在后一种情况下,对象是不可变的事实意味着函数可以识别出一个替代类型,该替代类型可以在给定其确切内容的情况下完成比更复杂的类型更多的任务。例如,Immutable2dMatrix
接口可以由ImmutableArrayBacked2dMatrix
类和ImmutableDiagonal2dMatrix
类实现。一个应该返回一个正方形Immutable2dMatrix
的函数可以决定返回一个ImmutableDiagonalMatrix
实例,如果除了主对角线外的所有元素都为零,或者一个ImmutableArrayBackedMatrix
如果不是。前者需要更少的存储空间,但接收方不应关心它们之间的区别。
返回Immutable2dMatrix
而不是具体的ImmutableArrayBackedMatrix
允许代码根据数组包含的内容选择返回类型;这也意味着如果应该返回数组的代码恰好持有适当的Immutable2dMatrix
实现,则可以简单地返回该实现,而无需构造新实例。在使用不可变对象时,这两个因素都可能是重要的“胜利”。
然而,在使用可变对象时,这两个因素都不起作用。可变数组生成时可能没有任何元素超出主对角线,但这并不意味着它永远不会有这样的元素。因此,虽然ImmutableDiagonalMatrix
实际上是Immutable2dMatrix
的子类型,但MutableDiagonalMatrix
不是Mutable2dMatrix
的子类型,因为后者可以接受超出主对角线的存储,而前者不能。此外,尽管不可变对象通常可以共享且应该共享,但可变对象通常不能共享。请求使用特定内容初始化的新可变集合的函数将需要创建一个新集合,无论其支持存储是否与所请求的类型匹配。
在接口上调用方法相比直接在对象上调用方法会带来一些轻微的开销,但通常不超过1或2个处理器指令。如果JIT知道该方法是final,则调用方法的开销甚至更低。对于大多数代码而言,这种开销无法测量,但对于java.utils中的低级方法可能会在某些代码中使用时成为问题。
此外,正如其他答案所指出的那样,返回对象的具体类型(即使被隐藏在接口后面)也会影响使用它的代码的性能。这种性能变化可能非常大,以至于调用软件无法正常工作。
显然,java.utils的作者无法知道调用Collections.list()的所有软件都对结果执行了什么操作,也无法在更改Collections.list()的实现后重新测试此软件。因此,即使类型系统允许,他们也不会更改Collections.list()的实现以返回不同类型的列表!
当你编写自己的软件时,(希望)你有自动化测试来覆盖所有代码,并且很好地了解代码之间的相互关系,包括知道性能存在问题的位置。在软件设计变化时,能够修改方法而不必更改调用者的能力具有很大的价值。
ArrayList
是未知的,即使查看Collections
的源代码,我们也无法回答这个问题。 - LafList
保留了元素添加的顺序,或者至少这是由List
接口定义的一般契约:“_有序集合(也称为序列)_”。 - Lafenum
不同。它们共享一个名称,但没有其他关联。 - yshavit