为什么 Java 映射不扩展 Collection?

令我惊讶的是 Map<?,?>不是 Collection<?>

我认为,如果它被宣布为这样的话,会有很大的意义:

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

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

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


感谢 Cletus 提供的最权威的答案,但我仍然想知道为什么,如果您已经可以将 Map<K,V>视为 Set<Map.Entries<K,V>>(通过 entrySet()) ,它不仅仅扩展了该接口。

如果一个 Map是一个 Collection,元素是什么? 唯一合理的答案是“键-值对”

没错,interface Map<K,V> extends Set<Map.Entry<K,V>>就太棒了!

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

但是如果是这样的话,那么为什么 entrySet是由接口指定的呢?它必须是有用的(而且我认为为这种立场辩护很容易!).

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

我不是说这就是 Map的全部!它可以和 应该保持所有其他方法(除了 entrySet,它现在是冗余的) !

47349 次浏览

来自 Java 集合 API 设计常见问题解答:

为什么 Map 不扩展 Collection?

这是设计好的,我们能感觉到 映射不是集合和 集合不是映射 对 Map 的扩展没有多大意义 集合接口(或 反对)。

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

集合可以扩展 地图,但这提出了一个问题: 什么是钥匙? 没有真正的 令人满意的答案,并强迫一个 通向一个不自然的界面。

地图可以被视为 键、值或对) ,以及这个事实 三个「藏品」均反映了香港特别行政区政府 视图操作”(keySet, 当它存在时,在 原则,可以将 List 视为 映射到元素的索引, 这东西有一个讨厌的特性 从 List 中删除元素 更改与每个 元素之前删除的元素。 这就是为什么我们看不到地图 清单行动。

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

Set<Map.Entry<String,String>>

允许:

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

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

Map需要唯一的键,所以这会违反这一点。或者,如果对条目的 Set强制使用唯一键,那么它就不是一般意义上的 Set。这是 Set还有更多限制。

可以说,Map.Entryequals()/hashCode()关系纯粹是关键,但即便如此也存在问题。更重要的是,它真的能增加价值吗?一旦开始研究角落案例,您可能会发现这种抽象会失败。

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

entrySet()存在的主要原因是为了简化遍历,这样您就不必遍历键,然后查找键。不要把它作为一个 Map应该是一个 Set条目(imho)的初步证据。

Cletus 的答案很好,但我想添加一个语义方法。将两者结合起来毫无意义,想象一下通过集合接口添加键-值对并且键已经存在的情况。Map 接口只允许一个与键关联的值。但是,如果您自动删除具有相同键的现有条目,则添加之后的集合的大小将与之前相同——这对于集合来说是非常意外的。

正是,< code > interface Map < K,V > 扩展 设置 < Map. Entry < K,V > > 会很棒!

事实上,如果是 implements Map<K,V>, Set<Map.Entry<K,V>>的话,我会同意的。.看起来很自然。但这样不太好,对吧?假设我们有 HashMap implements Map<K,V>, Set<Map.Entry<K,V>LinkedHashMap implements Map<K,V>, Set<Map.Entry<K,V>等等。.这很好,但是如果你有 entrySet(),没有人会忘记实现这个方法,你可以确定你可以为任何 Map 获得 entrySet,但是如果你希望实现者已经实现了这两个接口,你就不会忘记。

我不想使用 interface Map<K,V> extends Set<Map.Entry<K,V>>的原因很简单,因为将会有更多的方法。毕竟,他们是不同的东西,对不对?同样非常实际的是,如果我在 IDE 中命中 map.,我不想看到 .remove(Object obj).remove(Map.Entry<K,V> entry),因为我不能做 hit ctrl+space, r, return,也不能完成它。

我想 为什么是主观的。

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

public class Dictionary<TKey, TValue> : IDictionary<TKey, TValue>,
ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>,
IDictionary, ICollection, IEnumerable, ISerializable, IDeserializationCallback

在 Pharo Smalltak 也是如此:

Collection subclass: #Set
Set subclass: #Dictionary

但是有些方法是不对称的。例如,collect:采用关联(相当于一个条目) ,而 do:采用值。它们提供了另一种方法 keysAndValuesDo:来按条目迭代字典。Add:有一个联系,但 remove:被“抑制”了:

remove: anObject
self shouldNotImplement

所以这是完全可行的,但是会导致一些关于类层次结构的其他问题。

更好的是主观的。

虽然你已经得到了很多直接涵盖你的问题的答案,但我认为退一步来看这个问题可能会有所帮助。也就是说,不要专门研究 Java 库是如何被编写的,而要研究为什么它是这样被编写的。

这里的问题是继承只模拟 类型的公共性。如果你挑出两件看起来都“像集合”的东西,你可能会挑出8到10件它们的共同点。如果你选择一对不同的“集合类”的东西,他们也会有8或10个共同点-但他们不会是第一对 一样8或10的东西。

如果你看到一打左右不同的“收藏类”东西,几乎每一个都可能有8到10个共同特征,至少有一个共同特征——但是如果你看看在 每个上共享的其中一个,你几乎什么都没有留下。

这是一种继承(特别是单继承)不能很好地建模的情况。这两者之间没有明确的界限,哪些是真正的集合,哪些不是——但是如果您想定义一个有意义的 Collection 类,那么就必须忽略其中的一些。如果只省略其中的一部分,Collection 类将只能提供相当稀疏的接口。如果省略了更多内容,就能给它提供更丰富的界面。

有些人还选择说: “这种类型的集合支持操作 X,但是你不能使用它,因为它是从定义 X 的基类派生出来的,但是尝试使用派生类的 X 失败了(例如,抛出一个异常)。

这仍然留下了一个问题: 几乎不管哪些类被忽略,哪些类被放入,都必须在哪些类被放入和哪些类被放出之间划出一条明确的界限。无论你在哪里画这条线,你都会在一些类似 没错的事物之间留下一个清晰的,相当人工的划分。

Java 集合坏了。有一个缺失的接口,关系。因此,Map 扩展了关系扩展集。关系(也称为多映射)具有唯一的名称-值对。映射(又名“函数”) ,有唯一的名称(或键) ,当然映射到值。序列扩展了 Maps (其中每个键是一个 > 0的整数)。袋子(或多组)扩展了 Maps (其中每个键是一个元素,每个值是元素在袋子中出现的次数)。

这种结构允许一系列“集合”的交叉、联合等。因此,层次结构应该是:

                                Set


|


Relation


|


Map


/ \


Bag Sequence

Sun/Oracle/Java ppl-请下次正确使用。谢谢。

Map<K,V>不应延长 Set<Map.Entry<K,V>>,因为:

  • 您可以用相同的键向相同的 Map添加不同的 Map.Entry,但是
  • 你用相同的键向相同的 Set<Map.Entry>添加不同的 Map.Entry

如果您查看相应的数据结构,就可以很容易地猜到为什么 Map不是 Collection的一部分。每个 Collection存储单个值,而 Map存储键值对。因此,Collection接口中的方法对于 Map接口是不兼容的。例如,在 Collection中,我们有 add(Object o)。在 Map中这样的实现是什么。在 Map中使用这样的方法是没有意义的。相反,我们在 Map中有一个 Collection0方法。

addAll()remove()removeAll()方法的参数相同。因此,主要原因是 MapCollection存储数据的方式不同。 此外,如果您回想一下 Collection接口实现的 Iterable接口,也就是说,任何具有 .iterator()方法的接口都应该返回一个迭代器,它必须允许我们对存储在 Collection中的值进行迭代。那么对于 Map,这样的方法会返回什么呢?键迭代器还是值迭代器?这也说不通。

我们可以通过多种方式迭代 Map中的键和值存储,这就是它如何成为 Collection框架的一部分的。

直截了当。 集合是一个接口,它只需要一个 Object,而 Map 需要两个 Object。

Collection(Object o);
Map<Object,Object>