为什么Java Map不扩展Collection?

pol*_*nts 142 java oop collections

我对这Map<?,?>不是一个事实感到惊讶Collection<?>.

我认为如果宣布这样的话会有很多意义:

public interface Map<K,V> extends Collection<Map.Entry<K,V>>
Run Code Online (Sandbox Code Playgroud)

毕竟,一个Map<K,V>是集合Map.Entry<K,V>,不是吗?

那么为什么没有这样实现呢?


感谢Cletus提供了最权威的答案,但我仍然想知道为什么,如果您已经可以查看Map<K,V>as Set<Map.Entries<K,V>>(via entrySet()),它不仅仅是扩展该界面.

如果a Map是a Collection,那么元素是什么?唯一合理的答案是"键值对"

确切地说,interface Map<K,V> extends Set<Map.Entry<K,V>>会很棒!

但这提供了非常有限(并且不是特别有用)的Map抽象.

但如果是这种情况那么为什么entrySet界面指定?它必须以某种方式有用(我认为这个位置很容易争论!).

您不能询问给定键映射到的值,也不能删除给定键的条目而不知道它映射到的值.

我不是说这就是它的全部内容Map!它可以而且应该保留所有其他方法(除了entrySet现在多余的方法)!

cle*_*tus 121

来自Java Collections API Design FAQ:

为什么没有Map扩展Collection?

这是设计的.我们认为映射不是集合,集合不是映射.因此,Map扩展Collection接口(反之亦然)毫无意义.

如果Map是Collection,那么元素是什么?唯一合理的答案是"键值对",但这提供了非常有限(并且不是特别有用)的Map抽象.您不能询问给定键映射到的值,也不能删除给定键的条目而不知道它映射到的值.

收集可以扩展Map,但这提出了一个问题:关键是什么?没有真正令人满意的答案,强迫一个人导致不自然的界面.

地图可以被视为集合(键,值或对),这一事实反映在地图上的三个"集合视图操作"(keySet,entrySet和values)中.虽然原则上可以将List视为映射索引到元素的映射,但这具有令人讨厌的属性,即从列表中删除元素会更改与删除元素之前的每个元素关联的键.这就是为什么我们没有列表上的地图视图操作.

更新:我认为报价回答了大部分问题.值得强调的是关于一组条目不是特别有用的抽象的部分.例如:

Set<Map.Entry<String,String>>
Run Code Online (Sandbox Code Playgroud)

会允许:

set.add(entry("hello", "world"));
set.add(entry("hello", "world 2");
Run Code Online (Sandbox Code Playgroud)

(假设entry()创建Map.Entry实例的方法)

Maps需要唯一的密钥,所以这会违反这一点.或者如果你对一个Set条目强加了唯一的键,那么它Set通常不是一般意义上的.这是一个Set进一步的限制.

可以说你可以说equals()/ hashCode()关系Map.Entry纯粹是关键,但即便有问题.更重要的是,它真的增加了任何价值吗?一旦开始查看角落案例,您可能会发现这种抽象分解.

值得注意的是,HashSet它实际上是以一种HashMap方式实现的,而不是相反.这纯粹是一个实现细节,但仍然很有趣.

entrySet()存在的主要原因是简化遍历,因此您不必遍历密钥,然后查找密钥.不要把它作为表面证据表明a Map应该是Set条目(imho).

  • Zwei 提出了一个很好的观点。所有由 `add` 引起的复杂情况都可以像 `entrySet()` 视图那样处理:允许某些操作而不允许其他操作。当然,一个自然的反应是“什么样的 `Set` 不支持 `add`?” -- 好吧,如果这样的行为对于 `entrySet()` 是可以接受的,那么为什么它不能被 `this` 接受?也就是说,我 _am_ 已经基本相信为什么这不是我曾经认为的那么好,但我仍然认为它值得进一步辩论,如果只是为了丰富我自己对什么是好的 API/OOP 设计的理解。 (3认同)
  • 常见问题解答中引用的第一段很有趣:"我们感觉......因此......没有多大意义".我不觉得"感觉"和"感觉"形成一个结论; o) (3认同)
  • @aderchox如果你有一个“Set&lt;Pair&lt;K,V&gt;&gt;”,你不能询问它是否包含特定的“K”,因为“contains”的契约要求如果特定对象是则返回“true”在集合内,它必须是具有相同“K”*和*“V”匹配的“Pair”实例。这同样适用于“remove”合约。这正是“entrySet()”返回的集合的行为,其“contains”和“remove”方法根据“Map.Entry&lt;K,V&gt;”元素进行操作。这是一个与“Map”本身完全不同的 API,它有“containsKey”、“remove(key)”等,这就是它的全部内容。 (2认同)

ewe*_*nli 10

我想这就是为什么是主观的.

在C#中,我认为Dictionary扩展或至少实现了一个集合:

public class Dictionary<TKey, TValue> : IDictionary<TKey, TValue>, 
    ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, 
    IDictionary, ICollection, IEnumerable, ISerializable, IDeserializationCallback
Run Code Online (Sandbox Code Playgroud)

在Pharo Smalltak中:

Collection subclass: #Set
Set subclass: #Dictionary
Run Code Online (Sandbox Code Playgroud)

但是某些方法存在不对称性.例如,collect:将获取关联(相当于一个条目),同时do:取值.它们提供了另一种keysAndValuesDo:通过条目迭代字典的方法.Add:采取关联,但remove:已被"压制":

remove: anObject
self shouldNotImplement 
Run Code Online (Sandbox Code Playgroud)

所以它确实可行,但导致了关于类层次结构的一些其他问题.

更好的是主观的.


Jer*_*fin 10

虽然你已经得到了许多直接覆盖你的问题的答案,但我认为退一步可能是有用的,并且更一般地看一下这个问题.也就是说,不要特别注意如何编写Java库,并查看为什么以这种方式编写它.

这里的问题是继承只模拟一种类型的共性.如果你挑选出两件看起来像"像集合一样"的东西,你可能会挑选出他们共同拥有的8或10件东西.如果你选择一对不同的"收藏式"东西,它们也会有8或10个共同点 - 但它们与第一对不会是8或10个相同的东西.

如果你看一下十几个不同的"类似收藏"的东西,几乎每一个都可能有至少一个共同的8或10个特征 - 但如果你看看每个人共享的东西是什么他们中间几乎没有任何东西.

这是继承(尤其是单继承)不能很好地建模的情况.它们之间没有干净的分界线,哪些是真正的集合,哪些不是 - 但是如果你想要定义一个有意义的Collection类,你就会把它们中的一些留下来.如果只留下一些,你的Collection类只能提供相当稀疏的接口.如果你留下更多,你将能够给它一个更丰富的界面.

有些人还可以选择基本上说:"这种类型的集合支持操作X,但是你不允许使用它,通过从定义X的基类派生,但尝试使用派生类'X失败(例如, ,通过抛出异常).

这仍然存在一个问题:几乎无论你遗漏哪个以及你放弃了什么,你都必须在哪些类和哪些类之间划清界限.无论你在哪里绘制这条线,你都会在一些非常相似的东西之间留下一个清晰的,相当人为的区分.