为什么Java Map不能扩展集合?

我惊讶于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 ,现在是多余的)!

从Java Collections APIdeviseFAQ :

为什么地图不能扩展集合?

这是devise。 我们觉得映射不是集合,集合也不是映射。 因此,Map无法扩展Collection接口(反之亦然)。

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

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

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

更新:我认为引用回答了大部分问题。 值得强调的是关于条目集合的部分不是特别有用的抽象。 例如:

 Set<Map.Entry<String,String>> 

将允许:

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

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

Map s需要唯一的密钥,所以这将违反这一点。 或者,如果你在一Set条目上加上唯一的键,那么它就不是一般意义上的Set 。 这是一个进一步的限制。

可以说你可以说Map.Entryequals() / hashCode()关系纯粹是关键的,但即使这样也有问题。 更重要的是,它真的增加了什么价值吗? 一旦你开始看angular落案例,你可能会发现这个抽象的细节。

值得注意的是, HashSet实际上是作为一个HashMap实现的,而不是其他的方式。 这纯粹是一个实现的细节,但很有趣。

entrySet()存在的主要原因是为了简化遍历,所以不必遍历键,然后查找键。 不要把它作为表面证据certificate一个Map应该是一Set条目(imho)。

虽然你已经得到了很多直接覆盖你的问题的答案,但是我认为稍微回过头来看看这个问题也许是有用的。 也就是说,不要特别关注Java库如何被写入,并且看看为什么要这样写。

这里的问题是inheritance只模拟一种通用性。 如果你挑选出两件看起来像“collections品”的东西,那么你也许可以挑选出他们共有的8件或10件东西。 如果你挑出另外一对“collections品”的东西,他们也会有8或10个共同的东西 – 但它们不会和第一对相同。

如果你看十几种不同的“collections”的东西,几乎其中每一个都可能有至less有另外一个共同的8或10个特征 – 但是如果你看看一个共享的东西其中,你几乎什么也没有留下。

这是一种inheritance(尤其是单一inheritance)模式不好的情况。 在哪些是真正的集合,哪些不是真正的集合之间没有干净的分界线,但是如果你想定义一个有意义的集合类,你就不能将其中的一些排除在外。 如果只留下其中的一部分,Collection类将只能提供相当稀疏的界面。 如果你离开更多,你将能够给它一个更丰富的界面。

有些人也可以select基本上说:“这种types的集合支持操作X,但不允许使用它,通过从定义X的基类派生,但试图使用派生类X失败(例如,通过抛出exception)。

这仍然存在一个问题:几乎不pipe你放弃哪一个,哪一个放入什么类别,你都必须在什么是什么类,什么是什么之间划出一道牢固的界线。 无论你画出哪条线,你都会在一些非常相似的东西之间留下一个清晰的,相当人为的划分。

我猜为什么是主观的。

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

 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 

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

更好的是主观的。

cletus的答案很好,但我想添加一个语义方法。 要将两者结合起来没有任何意义,请考虑通过集合接口添加键值对并且键已经存在的情况。 Map接口只允许一个与密钥相关的值。 但是,如果您使用相同的键自动删除现有的条目,那么在添加与之前相同的大小之后,集合已经是非常意外的集合。

Java集合被打破。 有一个缺less的界面,即关系。 因此,Map extends Relation extends Set。 关系(也称为多图)具有唯一的名称 – 值对。 地图(也称为“函数”)具有唯一的名称(或键),这些名称当然映射到值。 序列扩展地图(其中每个键是一个大于0的整数)。 手袋(或多套)延伸地图(每个键是一个元素,每个值是元素出现在包里的次数)。

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

  Set | Relation | Map / \ Bag Sequence 

Sun / Oracle / Java ppl – 请在下次使用。 谢谢。

如果你看看相应的数据结构,你可以很容易地猜出为什么Map不是Collection的一部分。 每个Collection存储单个值,其中Map存储键值对。 因此, Collection接口中的方法与Map接口不兼容。 例如在Collection我们add(Object o)Map这种实现是什么? 在Map有这样一个方法是没有意义的。 相反,我们在Map有一个put(key,value)方法。

addAll()remove()removeAll()方法也是一样的。 所以主要原因是数据存储在MapCollection中的方式不同。 另外,如果您记得Collection接口实现的Iterable接口,即与.iterator()方法的任何接口都应该返回一个迭代器,它必须允许我们遍历Collection存储的值。 现在这样的方法返回一个Map ? 键迭代器或值迭代器? 这也没有任何意义。

有许多方法可以迭代Map键和值存储,也就是说它是Collection框架的一部分。

确实, interface Map<K,V> extends Set<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>>是简单的,因为会有更多的方法。 毕竟,他们是不同的东西,对吧? 也很实际,如果我打map. 在IDE中,我不想看到.remove(Object obj).remove(Map.Entry<K,V> entry)因为我不能按hit ctrl+space, r, return并完成它。

地图有三个不同的集合: 密钥集,条目集和一组键值对
您可以通过一个方法调用来获取三者中的任何一个。

只有一套键值对可以合理地说是“集合”,这是一个相当不自然的。
你可以说Map是一个Map.Entry的集合(实际上是一个集合),但它有点尴尬和怪异。
使用它的值集合或键集来识别集合更不容易。 所以,他们没有让人混淆,而是让所有这三个人都容易和平等地接近。

Map<K,V>不应该扩展Set<Map.Entry<K,V>>

  • 不能使用相同的键将不同的Map.Entry添加到同一个Map ,但是
  • 可以使用与Set<Map.Entry>相同的键添加不同的Map.Entry

简单而直接。 集合是期望只有一个对象的接口,其中地图需要两个。

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