为什么在接口中使用“可选方法”实现Java集合?


69

在扩展Java集合框架的第一个实现过程中,我很惊讶地看到集合接口包含声明为可选方法的方法。如果不支持,则期望实现者抛出UnsupportedOperationExceptions。这立即让我感到震惊,因为它是糟糕的API设计选择。

在阅读了约书亚·布洛赫(Joshua Bloch)出色的“有效Java”(Effective Java)书之后,后来得知他可能对这些决定负有责任,但似乎并没有遵循书中所遵循的原则。我想声明两个接口:Collection和MutableCollection,它通过“可选”方法扩展Collection,将导致可维护性更高的客户端代码。

还有的问题一个很好的总结在这里

是否有充分的理由为什么选择了可选方法而不是两个接口实现?


海事组织,没有充分的理由。C ++ STL由Stepanov在80年代初期创建。尽管它的可用性受到笨拙的C ++模板语法的限制,但与Java集合类相比,它是一致性和可用性的典范。
凯文·克莱恩

有一个C ++ STL“移植”到Java。但这是班级人数的5倍。出于这种考虑,我不认为较大的产品更一致,更实用。docs.oracle.com/javase/6/docs/technotes/guides/collections/...
m3th0dman

@ m3th0dman它在Java中要大得多,因为Java没有与C ++模板等效的功能。
凯文·克莱恩

也许这只是我开发的某种奇怪的样式,但是我的代码倾向于将所有集合视为“只读”(更确切地说,只是您计数或迭代的内容),实际创建集合的几种方法除外。无论如何,可能都是好的做法(尤其是为了并发)。对于我来说,这么多人抱怨的可选方法从来都不是真正的问题。泛型的“超级”和“扩展”的噩梦也从来没有成为一个问题。只想知道其他人是否使用这种常规做法?
user949300

Answers:


28

常见问题提供了答案。简而言之,他们看到了所需接口的潜在组合爆炸,包括可修改的,不可修改的视图,仅删除,仅添加,固定长度,不可变(用于线程化)等等,用于每种可能的已实现选项方法集。


6
如果Java具有const类似C ++ 的关键字,那么所有这些操作都可以避免。
Etienne de Martel

@Etienne:更好的是像Python这样的元类。然后,您可以以编程方式构建接口的组合数量。const的问题在于,它只能为您提供一两个维度:vector<int>, const vector<int>, vector<const int>, const vector<const int>。到目前为止好,但你尝试实现的图形,你想使图形结构不变,但节点属性修改,等等
尼尔摹

2
@Etienne,在我的待办事项清单上增加“ Learn Scala”的另一个原因!
glenviewjeff

2
我不明白的是,为什么他们没有制定一种can方法来测试是否可行?这样可以使界面简单快捷。
Mehrdad

3
@Etienne de Martel废话。这对组合爆炸有何帮助?
汤姆·哈特金

10

在我看来,Interface Segregation Principle当时的探索程度不如现在。在SOLID和ISP成为事实上的质量代码标准之前,这种处理方式(即您的界面包括所有可能的操作,并且您拥有“简并的”方法抛出不需要的异常)的方法很流行。


2
为什么要投票?有人不喜欢ISP吗?
韦恩·莫利纳

还值得注意的是,在支持方差的框架中,将接口的那些方面(可以协变或相反)与那些基本不变的方面分开具有巨大的优势。即使没有这样的支持,在不使用类型擦除的框架中​​,将接口的各个方面独立Count于类型也是很有价值的(例如,允许一个集合的集合而不必担心它包含什么类型的项目),但是在像Java这样基于类型擦除的框架中​​,这不是问题。
超级猫2012年

4

尽管有些人可能讨厌“可选方法”,但在许多情况下,它们可能比高度隔离的接口提供更好的语义。除其他事项外,它们还使对象有可能在其生命周期中获得能力或特性,或者使对象(尤其是包装对象)在构造时可能不知道应报告的确切能力。

虽然我几乎不会称赞Java收集类的优秀设计为典范,但我建议一个好的收集框架应在其基础上包括大量可选方法,以及向收集器询问其特性和能力的方法。这样的设计将允许单个包装器类与各种集合一起使用,而不会意外地遮盖基础集合可能具有的功能。如果方法不是可选的,则对于集合可能支持的功能的每种组合,都必须有一个不同的包装器类,否则在某些情况下某些包装器将无法使用。

例如,如果一个集合支持按索引写一个项目,或在末尾附加项目,但不支持在中间插入项目,那么想要将其封装在包装器中的代码将记录在其上执行的所有操作,则需要一个版本提供了所支持功能的确切组合的日志记录包装程序,或者如果没有可用的包装程序,则必须使用支持附加或按索引写入但不同时支持两者的包装程序。但是,如果统一的集合接口将所有三个方法都提供为“可选”,但随后包括指示哪些可选方法将可用的方法,则单个包装器类可以处理实现功能的任意组合的集合。当被问及它支持哪些功能时,包装器可以简单地报告封装的集合所支持的内容。

请注意,“可选功能”的存在在某些情况下可能允许聚合集合以某些方式实现某些功能,而这些方式的效率要比通过实现的存在来定义功能的方式更为有效。例如,假设使用了一种concatenate方法来形成另外两个对象的复合集合,其中第一个碰巧是具有1,000,000个元素的ArrayList,最后一个是只能从头开始迭代的二十个元素的集合。如果复合集合被要求提供第1,000,013个元素(索引1,000,012),则可以询问ArrayList包含多少个项(即1,000,000),从请求的索引中减去该项(产量12),从第二个元素读取和跳过十二个元素集合,然后返回下一个元素。

在这种情况下,即使复合集合没有立即返回按索引返回项目的方式,但要求复合集合中第1,000,013个项目比单独读取1,000,013个项目并忽略除最后一个项目之外的所有项目要快得多一。


@kevincline:您不同意引用的措辞吗?我认为接口实现可以描述其基本特征和功能的手段的设计是接口设计的最重要方面之一,即使它并不经常受到关注。如果接口的不同实现将具有不同的最佳方式来完成某些常见任务,则应该为关心性能的客户提供一种方法,以便在重要的情况下选择最佳方法。
超级猫

1
抱歉,评论不完整。我要说的是“'询问集合的方式'...”将应该由编译时检查的内容移至运行时。
凯文·克莱恩

@kevincline:如果客户需要具备一定的能力而又不能没有它,那么编译时检查会很有帮助。另一方面,在许多情况下,集合可能具有超出编译时保证的能力的能力。在某些情况下,它可能是有意义的有一个派生的接口,其合同规定的所有合法的实现派生接口必须支持特定的方法,这是在基本接口可选,但在情况下,代码将能够处理一些与否它具有某些功能...
supercat

...但是将从它存在的功能中受益,最好让代码询问对象是否支持该功能,而不是询问类型系统对象的类型是否承诺支持该功能。如果将广泛使用特定的“特定”接口,则可能AsXXX在基本接口中包含一个方法,如果实现该接口,它将返回在其上调用该对象的对象,如果可能的话,返回支持该接口的包装对象,或者抛出如果不是,则为例外。例如,一个ImmutableCollection界面可能需要按合同要求...
supercat

2
您的建议存在一个问题:如果对象的功能可以在其生存期内更改,则“先问再做”模式存在并发问题。(如果您无论如何都要冒例外的危险,您最好不打扰问……)
jhominal 2015年

-1

我将其归因于原始开发人员,当时他们还不了解。自1998年左右Java 2和Collections首次发布以来,我们在面向对象设计方面已经走了很长一段路。在OOP的早期,看起来似乎很糟糕的设计现在并没有那么明显。

但这可能是为了防止多余的铸造。如果是第二个接口,则必须强制转换集合实例以调用那些可选方法,这也很丑陋。现在,您将立即捕获UnsupportedOperationException并修复您的代码。但是,如果有两个接口,则必须在所有地方都使用instanceof和cast。也许他们认为这是一个有效的权衡。同样在Java 2天的早期中,由于instanceof的性能较慢而受到了极大的反对,他们可能一直在尝试防止过度使用它。

当然,这全是疯狂的猜测,我怀疑我们是否可以肯定地回答这个问题,除非其中一位原始的收藏家建筑师钟情于此。


7
什么铸造?如果某个方法返回a Collection而不是a MutableCollection,则表明它不是要修改的明确标志。我不知道为什么有人需要铸造它们。具有单独的接口意味着您将在编译时得到这类错误,而不是在运行时得到异常。越早得到错误,越好。
Etienne de Martel,

1
因为这样的方式不太灵活,所以集合库的最大好处之一就是您可以在任何地方返回高级接口,而不必担心实际的实现。如果您使用的是两个接口,那么您现在会更紧密地耦合在一起。在大多数情况下,您只想返回List而不是ImmutableList,因为您通常希望将其留给调用类来确定。
Jberg 2011年

6
如果我给您一个只读集合,那是因为我不希望对其进行修改。抛出异常并依赖文档会让人感觉很像补丁。在C ++中,我将简单地返回一个const对象,立即告诉用户该对象无法修改。
Etienne de Martel

7
1998年不是“ OO设计的早期”。
quant_dev
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.