我很惊讶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
除外,这现在是多余的)!
答案 0 :(得分:118)
来自Java Collections API Design FAQ:
为什么没有Map扩展Collection?
这是设计的。我们觉得 映射不是集合和 集合不是映射。因此,它 对于Map来说,扩展是没有意义的 收集界面(或副 亦然)。
如果Map是Collection,那么它是什么 元素?唯一合理的答案 是“键值对”,但这个 提供非常有限(而不是 特别有用)地图抽象。 你不能问给定密钥的价值 映射到,也不能删除条目 对于给定的密钥而不知道是什么 它映射到的值。
可以进行收集以进行扩展 地图,但这提出了一个问题: 有什么关键?没有真的 满意的答案,并强迫一个 导致不自然的界面。
地图可以被视为收藏集( 键,值或对),以及这个事实 反映在三个“收藏中 在地图上查看操作(keySet, entrySet和values)。虽然它是,在 原则,可以查看列表为 将索引映射到元素, 这有令人讨厌的财产 从列表中删除元素 更改与每个相关联的密钥 删除元素之前的元素。 这就是我们没有地图视图的原因 对列表进行操作。
更新:我认为引用可以解答大部分问题。值得强调的是关于一组条目不是特别有用的抽象的部分。例如:
Set<Map.Entry<String,String>>
允许:
set.add(entry("hello", "world"));
set.add(entry("hello", "world 2");
(假设entry()
方法创建Map.Entry
实例)
Map
需要使用唯一键,因此这会违反此规定。或者,如果您对Set
条目施加唯一键,则一般意义上它不是Set
。这是一个Set
,有进一步的限制。
可以说,equals()
的{{1}} / hashCode()
关系完全取决于关键,但即使这样也存在问题。更重要的是,它真的增加了任何价值吗?一旦你开始研究角落的情况,你可能会发现这种抽象被打破了。
值得注意的是Map.Entry
实际上是HashSet
实现的,而不是相反。这纯粹是一个实现细节,但仍然很有趣。
HashMap
存在的主要原因是简化遍历,因此您不必遍历密钥,然后查找密钥。不要将其视为entrySet()
应该是Map
条目(imho)的表面证据。
答案 1 :(得分:10)
我猜为什么是主观的。
在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
所以它确实可行,但导致了关于类层次结构的一些其他问题。
更好的是主观。
答案 2 :(得分:10)
虽然您已经得到了许多直接覆盖您的问题的答案,但我认为退一步可能会有所帮助,并且更一般地看一下这个问题。也就是说,不要专门看看如何编写Java库,并查看为什么以这种方式编写。
这里的问题是继承只模拟一种类型的共性。如果你挑选出两件看起来像“像集合一样”的东西,你可能会挑选出他们共同拥有的8或10件东西。如果你选择一组不同的“类似收藏”的东西,它们也会有8或10个共同点 - 但它们不会像第一对那样相同 8或10个东西
如果你看一下十几个不同的“类似收藏”的东西,几乎每一个都可能有至少另外一个8或10个特征的东西 - 但如果你看看共享的是什么在的每一个中,你几乎什么都没有。
这是继承(尤其是单继承)不能很好地建模的情况。它们之间没有干净的分界线,哪些是真正的集合,哪些不是 - 但是如果你想要定义一个有意义的Collection类,你就会把它们中的一些留下来。如果只留下一些,你的Collection类只能提供相当稀疏的接口。如果你留下更多,你将能够给它一个更丰富的界面。
有些人还可以选择基本上说:“这种类型的集合支持操作X,但是你不允许使用它,通过派生自定义X的基类,但尝试使用派生类'X失败(例如,通过抛出异常)。
这仍然存在一个问题:几乎不管你遗漏哪个以及你放入哪个,你将不得不在哪些类和哪些类之间划清界限。无论你在哪里绘制这条线,你都会在一些相当相似的东西之间留下一个清晰的,相当人为的分裂。
答案 3 :(得分:3)
cletus的答案很好,但我想添加语义方法。要将两者结合起来没有意义,请考虑通过集合接口添加键值对并且密钥已存在的情况。 Map-interface只允许与键关联的一个值。但是,如果您使用相同的键自动删除现有条目,则该集合在添加与之前相同的大小之后 - 对于集合来说非常意外。
答案 4 :(得分:2)
Java集合被破坏了。有一个缺失的接口,即Relation的接口。因此,Map扩展了Relation extends Set。关系(也称为多图)具有唯一的名称 - 值对。地图(又名“功能”)具有唯一的名称(或键),这些名称当然映射到值。序列扩展地图(其中每个键是一个整数> 0)。包(或多组)扩展地图(每个键是一个元素,每个值是元素在包中出现的次数)。
这种结构允许一系列“集合”的交集,结合等。因此,层次结构应该是:
Set
|
Relation
|
Map
/ \
Bag Sequence
Sun / Oracle / Java ppl - 请在下次使用它。感谢。
答案 5 :(得分:1)
如果查看相应的数据结构,您可以轻松猜出Map
不属于Collection
的原因。每个Collection
存储一个值,其中Map
存储键值对。因此Collection
接口中的方法与Map
接口不兼容。例如,在Collection
中我们有add(Object o)
。在Map
中会有什么样的实现。在Map
中使用这样的方法没有意义。相反,我们在put(key,value)
中有一个Map
方法。
同样的论点适用于addAll()
,remove()
和removeAll()
方法。因此,主要原因是数据存储在Map
和Collection
中的方式不同。
此外,如果您回忆Collection
接口实现的Iterable
接口,即任何具有.iterator()
方法的接口都应返回迭代器,该迭代器必须允许我们迭代存储在Collection
中的值。现在,Map
会为这种方法返回什么?键迭代器或值迭代器?这也没有意义。
我们可以通过多种方式迭代Map
中的键和值存储,这就是Collection
框架的一部分。
答案 6 :(得分:0)
确切地说,
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>>
的原因很简单,因为会有更多方法。毕竟,它们是不同的东西,对吧?同样非常实际的是,如果我在IDE中点击map.
,我不希望看到.remove(Object obj)
和.remove(Map.Entry<K,V> entry)
,因为我无法hit ctrl+space, r, return
执行此操作。
答案 7 :(得分:0)
Map<K,V>
不应该延伸Set<Map.Entry<K,V>>
,因为:
Map.Entry
添加到同一Map
,但Map.Entry
添加到同一个Set<Map.Entry>
。答案 8 :(得分:-1)
直接而简单。 Collection是一个只需要一个Object的接口,而Map需要两个。
Collection(Object o);
Map<Object,Object>