为什么要让接口拥有“可选操作”?

6

ImmutableSet 实现了 Set 接口。那些对于 ImmutableSet 没有意义的函数现在被称为 "可选操作",适用于 Set 接口。我猜这是为了像这种情况。所以,ImmutableSet 现在对许多可选操作抛出一个 UnsupportedOperationException 异常。

这对我来说似乎是反过来的。我被教导 Interface 是一种契约,使您可以将功能强加到不同的实现中。可选操作的方法似乎从根本上改变(矛盾?)接口的作用。如果今天实现这个,我会将 Set 接口分成两个接口:一个用于不可变操作,另一个扩展这些操作以进行更改。 (非常快速的解决方案)

我理解技术在不断变化。我不是说应该这样或那样。我的问题是,这种变化是否反映了 Java 中某些基础哲学的变化?它只是更多地用于兼容旧版本吗?我对接口的理解不完整吗?


1
“接口是一种契约,可以在不同的实现之间使用强制功能” - 这难道不是集合接口最终成功做到的吗?将异常作为功能的一部分可能不太受欢迎,但它是一种语言特性,当正确使用或实现集合时,没有任何可选项。您必须抛出/期望异常。我经常希望有更薄的接口或至少像.supportsRemoval()这样的方法。ImmutableSet扩展了Set,因为它指定了现在保证会抛出哪些方法。 - zapl
1个回答

8

Java集合框架API设计FAQ详细回答了这个问题:

问:为什么不在核心集合接口中直接支持不可变性,以便可以摆脱可选操作(和UnsupportedOperationException)? 答:这是整个API中最具争议的设计决策。显然,静态(编译时)类型检查非常有用,在Java中也是常见的。如果我们认为这是可行的,我们将会支持它。不幸的是,试图实现此目标会导致接口层次结构的爆炸,并且无法消除运行时异常的需要(尽管它们大大减少了)。
Doug Lea编写了一个流行的Java收集包,该包在其接口层次结构中反映了可变性差异,但基于用户对其收集包的使用经验,他不再认为这是一种可行的方法。用他的话来说(来自个人通信),“虽然我很难过地说,但强大的静态类型在Java中不适用于集合接口。”
为了详细说明问题,假设您想向层次结构添加可修改性的概念。您需要四个新接口:ModifiableCollection、ModifiableSet、ModifiableList和ModifiableMap。之前是一个简单的层次结构,现在是一个混乱的异层结构。此外,您需要一个新的Iterator接口,用于与不可修改的Collection一起使用,它不包含remove操作。现在您可以摆脱UnsupportedOperationException吗?不幸的是不能。
考虑数组。它们实现了大多数List操作,但不支持remove和add。它们是“固定大小”列表。如果您想在层次结构中捕获此概念,则必须添加两个新接口:VariableSizeList和VariableSizeMap。您不必添加VariableSizeCollection和VariableSizeSet,因为它们与ModifiableCollection和ModifiableSet相同,但出于一致性的考虑,您可能仍然选择添加它们。此外,您需要一种新的ListIterator类型,它不支持add和remove操作,以配合不可修改的List。现在我们已经有了十到十二个接口,加上两个新的Iterator接口,而不是最初的四个。我们完成了吗?没有。
考虑日志(例如错误日志、审计日志和可恢复数据对象的日志)。它们是自然的追加序列,支持除删除和设置(替换)之外的所有List操作。它们需要一个新的核心接口和一个新的迭代器。
那么不可变集合呢,与不可修改的集合相对应?(即,客户端无法更改的集合,并且永远不会因任何其他原因而更改)。许多人认为这是最重要的区别,因为它允许多个线程同时访问集合而无需同步。将此支持添加到类型层次结构中需要四个以上的接口。
现在我们已经有了二十个左右的接口和五个迭代器,几乎可以肯定仍然存在实际上不适合任何接口的集合。例如,Map返回的集合视图是自然的仅删除集合。此外,还有可能根据其值拒绝某些元素的集合,因此我们仍然没有摆脱运行时异常。
当一切都说完后,我们认为通过提供一组非常小的核心接口来抛出运行时异常是一个合理的工程妥协。
简而言之,拥有类似Set的接口和可选操作是为了防止需要大量不同接口而导致指数级爆炸。这并不像只有“不可变”和“可变”那么简单。Guava的ImmutableSet随后必须实现Set以与使用Set的所有其他代码进行互操作。虽然这并不理想,但确实没有更好的方法来解决它。

让Collection扩展ImmutableCollection(或给它一个更好的名字,如ReadableCollection)会不会更直观一些呢?这样就可以将更改Collection的方法保留在基本接口之外。按照现有情况,它违反了Liskov替换原则。 - Breandán Dalton
1
即使原则上它是可修改的,由于类型、值或容量限制,或者由Map返回的仅删除集合视图,Collection仍然会有可选操作,如上所述。同时,ReadableCollection接口将是无用的:您不能依赖于ReadableCollection是不可变的,因为它可能是可变的子类,您也不能将其传递给其他代码,期望它们无法修改它,因为这样的代码可以将其强制转换为Collection。因此,分离不会实现任何目的。 - Boann
虽然我不知道是否有更好的解决方案,但似乎接口有(曾经有过?)一个目的,而现在却将问题硬塞到了错误的解决方案中。你知道其他更好地解决这个问题的范例吗? - Carlos Bribiescas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接