是否有充分的理由使用Java的Collection接口?


11

我听到过这样的论点,即您应该使用可用的最通用的接口,以免与该接口的特定实现联系在一起。此逻辑是否适用于java.util.Collection之类的接口?

我宁愿看到如下内容:

List<Foo> getFoos()

要么

Set<Foo> getFoos()

代替

Collection<Foo> getFoos()

在最后一种情况下,我不知道我要处理哪种数据集,而在前两种情况下,我可以对顺序和唯一性做出一些假设。除了作为集合和列表的逻辑父代之外,java.util.Collection是否有用?

如果在进行代码审查时遇到使用Collection的代码,您将如何确定其用法是否合理,以及对使用更特定的界面替换它有何建议?


3
您是否偶然在java.security.cert中注意到一种返回类型是Collection<List<?>>?谈谈编码恐怖!
Macneil

1
@Macneil我不知道您指的是哪个类,但是这样的返回类型确实很明智。它本质上是告诉你,你有一个集合(即包含了一堆东西,没有一个合理的顺序)的列表(即包含的东西一个合理的顺序)的对象(即项目,其类型,我们不为静态知道不管什么原因)。对我来说似乎并不合理。
Zero3

Answers:


13

抽象比实现寿命长

通常,您的设计越抽象,则可能会越有用。因此,由于Collection更抽象,因为它是子接口,因此基于Collection的API设计比基于List的API设计更有可能保持有用。

但是,总体原则是使用最合适的抽象。因此,如果您的集合必须支持有序元素,则强制使用一个List,如果没有重复项,则强制使用Set,依此类推。

关于通用接口设计的说明

由于您对将Collection接口与泛型一起使用感兴趣,因此以下内容可能会有所帮助。Joshua Bloch撰写的有效Java建议在设计将依赖泛型的接口时建议采用以下方法:生产者扩展,消费者超级用户

这也称为PECS规则。本质上,如果将产生数据的通用集合传递给您的类,则签名应如下所示:

public void pushAll(Collection<? extends E> producerCollection) {}

因此,输入类型可以是E或E的任何子类(在Java语言中,E被定义为其自身的超类和子类)。

相反,传递给使用数据的通用集合应具有如下签名:

public void popAll(Collection<? super E> consumerCollection) {}

该方法将正确对待E.总体的任何超类,使用这种方法会让你的界面并不值得大惊小怪给用户,因为你可以在传递Collection<Number>Collection<Integer>,让他们正确对待。


6

Collection接口,最宽松的形式Collection<?>,是伟大的参数,你接受。基于Java库本身的使用,它作为参数类型比返回类型更常见。

对于返回类型,我认为您的观点是正确的:如果希望人们访问它,那么他们应该知道所执行操作的顺序(从Big-O角度)。我将遍历Collection返回的并将其添加到另一个Collection中,但是调用它似乎有点疯狂contains,不知道它是O(1),O(log n)还是O(n)操作。当然,仅因为拥有a Set并不意味着它是哈希集或排序集,但是在某些时候,您将假设接口已被合理实现(然后,如果您的假设,则需要去计划B)显示为不正确)。

正如Tom所提到的,有时您需要返回一个Collection来维护封装:您不希望实现细节泄漏出去,即使您可以返回更具体的内容。或者,在Tom提到的情况下,您可以返回更具体的容器,但随后必须构造它。


2
我认为第二点有点弱。无论集合是List还是List,您都不知道集合将如何执行-因为它们只是抽象。除非您有具体的期末课程,否则您将无法真正分辨。
Mark H

同样,如果您只知道某物是一个集合,则不知道它是否可以包含重复项。反过来说,一次适合使用返回Collection的情况是,如果您有一个不包含重复项且没有明显顺序(自然是Set)的collection,但是出于某种充分的理由,应该使用returning方法的实现使用列表。您不希望返回一个List,因为这意味着顺序很重要,但是如果不经过制作一个Set的繁琐工作,就不能返回Set。因此,您返回一个集合。
汤姆·安德森

@汤姆:好点!
Macneil's

5

我会从完全相反的角度来看它,然后问:

如果在进行代码审查时遇到使用List <>的代码,您将如何确定其用法是否合理?

证明这一点很容易。当您需要集合不提供的某些功能时,可以使用列表。如果您不需要额外的功能-您有什么理由?(而且我不会购买“我更喜欢看”)

在很多情况下,您会将集合用于只读目的,一次填充所有对象,然后对其进行完全迭代-您是否需要手动索引该对象?

举一个真实的例子。说我在数据库上执行一个简单的查询。(SELECT id,name,rep FROM people WHERE name LIKE '%squared')我获取相关数据,填充Person对象并将它们放入PersonList中)

  • 我需要按索引访问吗?-毫无意义。索引和ID之间没有映射。
  • 我需要在索引处插入吗?-不,如果我要添加的话,DBMS将决定将其放置在哪里。

那么对于这些​​额外的方法我有什么理由呢?(无论如何我的PersonList中都不会实现)


有道理。我想我的问题是针对特定实例的,在该特定实例中,在进行代码审查时,我一直看到DAO返回Collections,但是我知道这些DAO调用总是会返回实体集;我的争论是,在这些情况下,返回类型表示唯一性,并且此信息对必须使用该方法的人很有帮助(例如,我不必检查重复项)。
费尔

如果您查询了一个数据库,则将两个结果对象与equals()进行比较根本不会产生true-因此,您需要另一种比较对象以进行重复的方法(例如,它们是否具有相同的名称,相同的ID,都?)。如果要删除重复项本身,则需要告诉您的DAO如何比较它们-但是由于您是用户来确定是否存在重复项-只需使用调用代码中的集合来进行操作就更容易了。(为了避免更多的抽象层来通知DAO如何在地球上执行所有可能的相等性检查。)
Mark H 2010年

同意,但我们正在使用Hibernate,并确保我们的实体实现equals()。因此,当DAO返回实体时,我们可以非常快速地执行新的HashSet()。addAll(results)并将其返回给被调用的方法。
费尔
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.