让我惊讶的是 Map
不是 Collection
。
我认为如果这样声明它会很有意义:
public interface Map<K,V> extends Collection<Map.Entry<K,V>>
毕竟, Map
是 Map.Entry
,不是它?
那么有没有充分的理由不这样实施呢?
感谢 Cletus 提供了最权威的答案,但我仍然想知道为什么,如果您已经可以将 Map
查看为 Set >
(通过 entrySet()
),它不仅仅扩展该接口。
如果Map
是一个Collection
,那么元素是什么?唯一合理的答案是“键值对”
确切地说,接口Map extends Set>
会很棒!
但这提供了非常有限(并且不是特别有用)的Map
抽象。
但如果是这样的话,为什么接口要指定 entrySet
呢?它一定是有用的(我认为很容易争论这个立场!)。
您不能询问给定键映射到什么值,也不能在不知道给定键映射到什么值的情况下删除它的条目。
我并不是说这就是 Map
的全部内容!它可以而且应该保留所有其他方法(除了 entrySet
,它现在是多余的)!
I was surprised by the fact that Map<?,?>
is not a Collection<?>
.
I thought it'd make a LOT of sense if it was declared as such:
public interface Map<K,V> extends Collection<Map.Entry<K,V>>
After all, a Map<K,V>
is a collection of Map.Entry<K,V>
, isn't it?
So is there a good reason why it's not implemented as such?
Thanks to Cletus for a most authoritative answer, but I'm still wondering why, if you can already view a Map<K,V>
as Set<Map.Entries<K,V>>
(via entrySet()
), it doesn't just extend that interface instead.
If a Map
is a Collection
, what are the elements? The only reasonable answer is "Key-value pairs"
Exactly, interface Map<K,V> extends Set<Map.Entry<K,V>>
would be great!
but this provides a very limited (and not particularly useful) Map
abstraction.
But if that's the case then why is entrySet
specified by the interface? It must be useful somehow (and I think it's easy to argue for that position!).
You can't ask what value a given key maps to, nor can you delete the entry for a given key without knowing what value it maps to.
I'm not saying that that's all there is to it to Map
! It can and should keep all the other methods (except entrySet
, which is redundant now)!
发布评论
评论(10)
来自 Java Collections API 设计常见问题解答 :
更新:我认为这句话回答了大部分问题。值得强调的是,条目集合并不是一个特别有用的抽象。例如:
将允许:(
假设创建
Map.Entry
实例的entry()
方法)Map
需要唯一的键,因此这将违反此规定。或者,如果您在Set
条目上施加唯一键,那么它实际上并不是一般意义上的Set
。它是一个带有进一步限制的Set
。可以说,您可以说
Map.Entry
的equals()
/hashCode()
关系纯粹是在键上,但即使这样也有问题。更重要的是,它真的能增加任何价值吗?一旦您开始研究极端情况,您可能会发现这种抽象被打破了。值得注意的是,
HashSet
实际上是作为HashMap
实现的,而不是相反。这纯粹是一个实现细节,但仍然很有趣。entrySet()
存在的主要原因是为了简化遍历,这样您就不必遍历键然后查找键。不要将其视为Map
应该是一个Set
条目的表面证据(恕我直言)。From the Java Collections API Design FAQ:
Update: I think the quote answers most of the questions. It's worth stressing the part about a collection of entries not being a particularly useful abstraction. For example:
would allow:
(assuming an
entry()
method that creates aMap.Entry
instance)Map
s require unique keys so this would violate this. Or if you impose unique keys on aSet
of entries, it's not really aSet
in the general sense. It's aSet
with further restrictions.Arguably you could say the
equals()
/hashCode()
relationship forMap.Entry
was purely on the key but even that has issues. More importantly, does it really add any value? You may find this abstraction breaks down once you start looking at the corner cases.It's worth noting that the
HashSet
is actually implemented as aHashMap
, not the other way around. This is purely an implementation detail but is interesting nonetheless.The main reason for
entrySet()
to exist is to simplify traversal so you don't have to traverse the keys and then do a lookup of the key. Don't take it as prima facie evidence that aMap
should be aSet
of entries (imho).虽然您已经得到了相当直接地涵盖您的问题的许多答案,但我认为退一步,更普遍地看待这个问题可能会很有用。也就是说,不要专门看 Java 库是如何编写的,而是看看为什么要这样编写。
这里的问题是继承仅模拟一种类型的共性。如果你挑出两件看起来都“像收藏品”的东西,你可能会挑出 8 到 10 件它们有共同点的东西。如果你挑选出一对不同的“类似收藏”的东西,它们也会有 8 或 10 个共同点,但它们不会与第一对有 8 或 10 个相同的东西。
如果你观察十几个不同的“类似集合”的东西,几乎每一个都可能与至少一个其他东西有大约 8 或 10 个共同的特征 - 但如果你看看之间共享的东西>每一个,你几乎一无所有。
这是继承(尤其是单继承)无法很好地建模的情况。哪些是真正的集合,哪些不是,之间没有明确的分界线——但是如果您想定义一个有意义的 Collection 类,您就不得不忽略其中一些集合。如果只保留其中的几个,您的 Collection 类将只能提供相当稀疏的接口。如果您保留更多内容,您将能够为其提供更丰富的界面。
有些还选择基本上说:“这种类型的集合支持操作 X,但不允许通过从定义 X 的基类派生来使用它,但尝试使用派生类' X 会失败(例如,通过抛出异常),
这仍然留下一个问题:几乎无论您遗漏哪些内容以及添加哪些内容,您都必须在哪些类在其中和哪些类不在其中之间划清界限。当你画出这条线时,你就会在一些非常相似的事物之间留下一个清晰的、相当人为的划分。
While you've gotten a number of answers that cover your question fairly directly, I think it might be useful to step back a bit, and look at the question a bit more generally. That is, not to look specifically at how the Java library happens to be written, and look at why it's written that way.
The problem here is that inheritance only models one type of commonality. If you pick out two things that both seem "collection-like", you can probably pick out a 8 or 10 things they have in common. If you pick out a different pair of "collection-like" things, they'll also 8 or 10 things in common -- but they won't be the same 8 or 10 things as the first pair.
If you look at a dozen or so different "collection-like" things, virtually every one of them will probably have something like 8 or 10 characteristics in common with at least one other one -- but if you look at what's shared across every one of them, you're left with practically nothing.
This is a situation that inheritance (especially single inheritance) just doesn't model well. There's no clean dividing line between which of those are really collections and which aren't -- but if you want to define a meaningful Collection class, you're stuck with leaving some of them out. If you leave only a few of them out, your Collection class will only be able to provide quite a sparse interface. If you leave more out, you'll be able to give it a richer interface.
Some also take the option of basically saying: "this type of collection supports operation X, but you're not allowed to use it, by deriving from a base class that defines X, but attempting to use the derived class' X fails (e.g., by throwing an exception).
That still leaves one problem: almost regardless of which you leave out and which you put in, you're going to have to draw a hard line between what classes are in and what are out. No matter where you draw that line, you're going to be left with a clear, rather artificial, division between some things that are quite similar.
我想原因是主观的。
在 C# 中,我认为
Dictionary
扩展或至少实现了一个集合:在 Pharo Smalltak 中也是如此:
但是某些方法存在不对称性。例如,
collect:
将采用关联(相当于条目),而do:
则采用值。他们提供了另一种方法keysAndValuesDo:
来按条目迭代字典。Add:
需要关联,但remove:
已被“抑制”:因此它绝对可行,但会导致有关类层次结构的其他一些问题。
什么更好是主观的。
I guess the why is subjective.
In C#, I think
Dictionary
extends or at least implements a collection:In Pharo Smalltak as well:
But there is an asymmetry with some methods. For instance,
collect:
will takes association (the equivalent of an entry), whiledo:
take the values. They provide another methodkeysAndValuesDo:
to iterate the dictionary by entry.Add:
takes an association, butremove:
has been "suppressed":So it's definitively doable, but leads to some other issues regarding the class hierarchy.
What is better is subjective.
cletus的答案很好,但我想添加一种语义方法。将两者结合起来是没有意义的,请考虑通过集合接口添加键值对并且键已经存在的情况。 Map 接口只允许一个值与键关联。但是,如果您自动删除具有相同键的现有条目,则添加后的集合将具有与之前相同的大小 - 对于集合来说非常意外。
The answer of cletus is good, but I want to add a semantic approach. To combine both makes no sense, think of the case you add a key-value-pair via the collection interface and the key already exists. The Map-interface allows only one value associated with the key. But if you automatically remove the existing entry with the same key, the collection has after the add the same size as before - very unexpected for a collection.
Java 集合已损坏。缺少一个接口,即关系接口。因此,Map 扩展了 Relation 扩展了 Set。关系(也称为多重映射)具有唯一的名称-值对。映射(又名“函数”)具有唯一的名称(或键),它们当然会映射到值。序列扩展了映射(其中每个键都是> 0的整数)。包(或多集)扩展了映射(其中每个键是一个元素,每个值是该元素在包中出现的次数)。
该结构允许一系列“集合”的交集、并集等。因此,层次结构应该是:
Sun/Oracle/Java ppl - 请下次正确设置。谢谢。
Java collections are broken. There is a missing interface, that of Relation. Hence, Map extends Relation extends Set. Relations (also called multi-maps) have unique name-value pairs. Maps (aka "Functions"), have unique names (or keys) which of course map to values. Sequences extend Maps (where each key is an integer > 0). Bags (or multi-sets) extend Maps (where each key is an element and each value is the number of times the element appears in the bag).
This structure would allow intersection, union etc. of a range of "collections". Hence, the hierarchy should be:
Sun/Oracle/Java ppl - please get it right next time. Thanks.
Map
不应扩展Set>
因为:Map
具有相同键的Map.Entry
,但Map.Entry
使用相同的键指向相同的Set
。即它不认为地图“是”地图条目集。
Map<K,V>
should not extendSet<Map.Entry<K,V>>
since:Map.Entry
s with the same key to the sameMap
, butMap.Entry
s with the same key to the sameSet<Map.Entry>
.i.e. it doesn't hold that a map "is-a" set-of-map-entries.
如果您查看相应的数据结构,您可以轻松猜测为什么
Map
不是Collection
的一部分。每个Collection
存储单个值,而Map
存储键值对。因此Collection
接口中的方法与Map
接口不兼容。例如,在Collection
中,我们有add(Object o)
。Map
中的这种实现是什么?在Map
中拥有这样的方法是没有意义的。相反,我们在Map
中有一个put(key,value)
方法。同样的参数也适用于
addAll()
、remove()
和removeAll()
方法。所以主要原因是Map
和Collection
中数据存储方式的不同。另外,如果您还记得
Collection
接口实现了Iterable
接口,即任何具有.iterator()
方法的接口都应该返回一个迭代器,该迭代器必须允许我们迭代存储在集合
中的值。现在,这样的方法会为Map
返回什么?键迭代器还是值迭代器?这也没有道理。我们可以通过多种方式迭代
Map
中的键和值存储,这就是它成为Collection
框架的一部分的方式。If you look at the respective data structure you can easily guess why
Map
is not a part ofCollection
. EachCollection
stores a single value where as aMap
stores key-value pair. So methods inCollection
interface are incompatible forMap
interface. For example inCollection
we haveadd(Object o)
. What would be such implementation inMap
. It doesn't make sense to have such a method inMap
. Instead we have aput(key,value)
method inMap
.Same argument goes for
addAll()
,remove()
, andremoveAll()
methods. So the main reason is the difference in the way data is stored inMap
andCollection
.Also if you recall
Collection
interface implementedIterable
interface i.e. any interface with.iterator()
method should return an iterator which must allow us to iterate over the values stored in theCollection
. Now what would such method return for aMap
? Key iterator or a Value iterator? This does not make sense either.There are ways in which we can iterate over keys and values stores in a
Map
and that is how it is a part ofCollection
framework.实际上,如果它
实现了 Map、Set>
,那么我倾向于同意......这看起来甚至很自然。但这效果不太好,对吧?假设我们有HashMap 实现了 Map、Set
,LinkedHashMap 实现了 Map、Set 等...这一切都很好,但是如果您有
entrySet()
,那么没有人会忘记实现该方法,并且您可以确定您可以获得entrySet任何 Map,而如果您希望实现者已经实现了这两个接口,则不是......我不想拥有接口 Map 的原因是extends Set> 很简单,因为方法会多一些。毕竟,它们是不同的东西,对吧?同样非常实际的是,如果我在 IDE 中点击
map.
,我不想看到.remove(Object obj)
和.remove(Map.Entry< ;K,V> 条目)
因为我无法点击 ctrl+space, r, return
并完成它。Actually, if it were
implements Map<K,V>, Set<Map.Entry<K,V>>
, then I tend to agree.. It seems even natural. But that doesn't work very well, right? Let's say we haveHashMap implements Map<K,V>, Set<Map.Entry<K,V>
,LinkedHashMap implements Map<K,V>, Set<Map.Entry<K,V>
etc... that is all good, but if you hadentrySet()
, nobody will forget to implement that method, and you can be sure that you can get entrySet for any Map, whereas you aren't if you are hoping that the implementor has implemented both interfaces...The reason I don't want to have
interface Map<K,V> extends Set<Map.Entry<K,V>>
is simply, because there will be more methods. And after all, they are different things, right? Also very practically, if I hitmap.
in IDE, I don't want to see.remove(Object obj)
, and.remove(Map.Entry<K,V> entry)
because I can't dohit ctrl+space, r, return
and be done with it.add(E e)
方法不支持像Map接口的put(k,v)
那样的键值对代码>方法。add(E e)
method of collection interface doesn't support key-value pair like map interface'sput(k,v)
method.直接又简单。
Collection 是一个只需要一个对象的接口,而 Map 需要两个对象。
Straight and simple.
Collection is an interface which is expecting only one Object, whereas Map requires Two.