为什么 Java Map 不扩展 Collection?

2022-08-31 07:23:49

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

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

public interface Map<K,V> extends Collection<Map.Entry<K,V>>

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

那么,有没有一个很好的理由来解释为什么它没有这样实现呢?


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

如果 a 是 ,那么元素是什么?唯一合理的答案是“键值对”MapCollection

没错,会很棒!interface Map<K,V> extends Set<Map.Entry<K,V>>

但这提供了一个非常有限(也不是特别有用)的抽象。Map

但是,如果是这种情况,那么为什么由接口指定?它必须以某种方式有用(我认为很容易为这个立场辩护!entrySet

您无法询问给定键映射到哪个值,也不能在不知道给定键映射到什么值的情况下删除给定键的条目。

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


答案 1

来自 Java Collections API Design FAQ

为什么 Map 不扩展集合?

这是设计使然。我们认为映射不是集合,集合不是映射。因此,Map 扩展集合接口(反之亦然)没有多大意义。

如果地图是集合,那么元素是什么?唯一合理的答案是“键值对”,但这提供了一个非常有限(也不是特别有用)的Map抽象。您无法询问给定键映射到哪个值,也不能在不知道给定键映射到什么值的情况下删除给定键的条目。

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

地图可以查看为集合(键、值或对),这一事实反映在地图上的三个“集合视图操作”(键集、条目集和值)中。虽然原则上可以将 List 视为将索引映射到元素的 Map,但这具有令人讨厌的属性,即从 List 中删除元素会更改与已删除元素之前的每个元素关联的 Key。这就是为什么我们没有对列表进行地图视图操作的原因。

更新:我认为这句话回答了大多数问题。值得强调的是,关于条目集合的部分并不是一个特别有用的抽象。例如:

Set<Map.Entry<String,String>>

将允许:

set.add(entry("hello", "world"));
set.add(entry("hello", "world 2"));

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

Maps 需要唯一键,因此这将违反此规定。或者,如果您对条目施加唯一键,则它实际上不是一般意义上的键。这是一个有进一步限制的。SetSetSet

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

值得注意的是,实际上实现为 一个 ,而不是相反。这纯粹是一个实现细节,但仍然很有趣。HashSetHashMap

存在的主要原因是简化遍历,这样您就不必遍历键,然后查找键。不要把它当作初步证据,证明a应该是条目的(恕我直言)。entrySet()MapSet


答案 2

虽然你已经得到了一些相当直接的答案,但我认为退后一步,更广泛地看待这个问题可能是有用的。也就是说,不要专门研究Java库是如何编写的,而是要看看为什么它以这种方式编写。

这里的问题是,继承只对种类型的共性进行建模。如果你挑出两件看起来都“像收藏”的东西,你可能会挑出它们有共同点的8或10件事。如果你挑出一对不同的“类似收藏”的东西,它们也会有8或10个共同点——但它们不会和第一对相同8或10个东西。

如果你看一下十几个不同的“类似收藏”的东西,几乎每一个都可能与至少一个其他特征有8或10个共同的特征 —— 但是如果你看看它们之间共享的东西,你几乎什么都没有。

在这种情况下,继承(尤其是单一继承)不能很好地建模。在哪些是真正的集合和哪些不是之间没有明确的分界线 - 但是如果你想定义一个有意义的集合类,你只能省略其中的一些。如果只省略其中的几个,则集合类将只能提供相当稀疏的接口。如果您遗漏更多内容,您将能够为其提供更丰富的界面。

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

这仍然留下了一个问题:几乎无论你遗漏了哪些,你放了哪些,你都必须在哪些类和哪些类出之间划出一条硬线。无论你在哪里画这条线,你都会在一些非常相似的东西之间留下一个清晰的,相当人为的划分。