为什么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.Entry
的equals()
/ 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()
方法也是一样的。 所以主要原因是数据存储在Map
和Collection
中的方式不同。 另外,如果您记得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>