为什么Java Map不扩展Collection?

发布于 2024-08-29 06:56:39 字数 1139 浏览 5 评论 0 原文

让我惊讶的是 Map 不是 Collection

我认为如果这样声明它会很有意义:

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

毕竟, MapMap.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)!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

江心雾 2024-09-05 06:56:39

来自 Java Collections API 设计常见问题解答

为什么 Map 不扩展 Collection?

这是设计使然。我们觉得
映射不是集合并且
集合不是映射。因此,它
Map 扩展没有什么意义
Collection 接口(或副
反之亦然)。

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

可以延长收藏时间
地图,但这提出了一个问题:
钥匙是什么?确实没有
满意的答复,逼迫
导致界面不自然。

地图可以被视为集合(
键、值或对),这一事实
体现在三个“集合”
查看地图上的操作”(keySet、
条目集和值)。虽然如此,在
原则上,可以将列表视为
将索引映射到元素的 Map,
这有一个令人讨厌的特性
从列表中删除一个元素
更改与每个相关的密钥
被删除元素之前的元素。
这就是为什么我们没有地图视图
对列表的操作。

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

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 条目的表面证据(恕我直言)。

From the Java Collections API Design FAQ:

Why doesn't Map extend Collection?

This was by design. We feel that
mappings are not collections and
collections are not mappings. Thus, it
makes little sense for Map to extend
the Collection interface (or vice
versa).

If a Map is a Collection, what are the
elements? The only reasonable answer
is "Key-value pairs", but this
provides a very limited (and not
particularly useful) Map abstraction.
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.

Collection could be made to extend
Map, but this raises the question:
what are the keys? There's no really
satisfactory answer, and forcing one
leads to an unnatural interface.

Maps can be viewed as Collections (of
keys, values, or pairs), and this fact
is reflected in the three "Collection
view operations" on Maps (keySet,
entrySet, and values). While it is, in
principle, possible to view a List as
a Map mapping indices to elements,
this has the nasty property that
deleting an element from the List
changes the Key associated with every
element before the deleted element.
That's why we don't have a map view
operation on Lists.

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:

Set<Map.Entry<String,String>>

would allow:

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

(assuming an entry() method that creates a Map.Entry instance)

Maps require unique keys so this would violate this. Or if you impose unique keys on a Set of entries, it's not really a Set in the general sense. It's a Set with further restrictions.

Arguably you could say the equals()/hashCode() relationship for Map.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 a HashMap, 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 a Map should be a Set of entries (imho).

变身佩奇 2024-09-05 06:56:39

虽然您已经得到了相当直接地涵盖您的问题的许多答案,但我认为退一步,更普遍地看待这个问题可能会很有用。也就是说,不要专门看 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.

假情假意假温柔 2024-09-05 06:56:39

我想原因是主观的。

在 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 

因此它绝对可行,但会导致有关类层次结构的其他一些问题。

什么更好是主观的。

I guess the why is subjective.

In C#, I think Dictionary extends or at least implements a collection:

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

In Pharo Smalltak as well:

Collection subclass: #Set
Set subclass: #Dictionary

But there is an asymmetry with some methods. For instance, collect: will takes association (the equivalent of an entry), while do: take the values. They provide another method keysAndValuesDo: to iterate the dictionary by entry. Add: takes an association, but remove: has been "suppressed":

remove: anObject
self shouldNotImplement 

So it's definitively doable, but leads to some other issues regarding the class hierarchy.

What is better is subjective.

叹倦 2024-09-05 06:56:39

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.

极致的悲 2024-09-05 06:56:39

Java 集合已损坏。缺少一个接口,即关系接口。因此,Map 扩展了 Relation 扩展了 Set。关系(也称为多重映射)具有唯一的名称-值对。映射(又名“函数”)具有唯一的名称(或键),它们当然会映射到值。序列扩展了映射(其中每个键都是> 0的整数)。包(或多集)扩展了映射(其中每个键是一个元素,每个值是该元素在包中出现的次数)。

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

                                Set

                                 |

                              Relation

                                 |

                                Map

                                / \

                             Bag Sequence

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:

                                Set

                                 |

                              Relation

                                 |

                                Map

                                / \

                             Bag Sequence

Sun/Oracle/Java ppl - please get it right next time. Thanks.

春庭雪 2024-09-05 06:56:39

Map 不应扩展 Set> 因为:

  • 不能添加不同的相同 Map 具有相同键的 Map.Entry,但
  • 可以添加不同的 Map.Entry使用相同的键指向相同的 Set

即它不认为地图“是”地图条目集。

Map<K,V> should not extend Set<Map.Entry<K,V>> since:

  • You can't add different Map.Entrys with the same key to the same Map, but
  • You can add different Map.Entrys with the same key to the same Set<Map.Entry>.

i.e. it doesn't hold that a map "is-a" set-of-map-entries.

做个少女永远怀春 2024-09-05 06:56:39

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

同样的参数也适用于 addAll()remove()removeAll() 方法。所以主要原因是MapCollection中数据存储方式的不同。
另外,如果您还记得 Collection 接口实现了 Iterable 接口,即任何具有 .iterator() 方法的接口都应该返回一个迭代器,该迭代器必须允许我们迭代存储在集合中的值。现在,这样的方法会为 Map 返回什么?键迭代器还是值迭代器?这也没有道理。

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

If you look at the respective data structure you can easily guess why Map is not a part of Collection. Each Collection stores a single value where as a Map stores key-value pair. So methods in Collection interface are incompatible for Map interface. For example in Collection we have add(Object o). What would be such implementation in Map. It doesn't make sense to have such a method in Map. Instead we have a put(key,value) method in Map.

Same argument goes for addAll(), remove(), and removeAll() methods. So the main reason is the difference in the way data is stored in Map and Collection.
Also if you recall Collection interface implemented Iterable interface i.e. any interface with .iterator() method should return an iterator which must allow us to iterate over the values stored in the Collection. Now what would such method return for a Map? 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 of Collection framework.

客…行舟 2024-09-05 06:56:39

没错,接口Map;延伸
设置>
就太好了!

实际上,如果它实现了 Map、Set>,那么我倾向于同意......这看起来甚至很自然。但这效果不太好,对吧?假设我们有 HashMap 实现了 Map、SetLinkedHashMap 实现了 Map、Set 等...这一切都很好,但是如果您有 entrySet(),那么没有人会忘记实现该方法,并且您可以确定您可以获得entrySet任何 Map,而如果您希望实现者已经实现了这两个接口,则不是......

我不想拥有接口 Map 的原因是extends Set> 很简单,因为方法会多一些。毕竟,它们是不同的东西,对吧?同样非常实际的是,如果我在 IDE 中点击 map.,我不想看到 .remove(Object obj).remove(Map.Entry< ;K,V> 条目) 因为我无法点击 ctrl+space, r, return 并完成它。

Exactly, interface Map<K,V> extends
Set<Map.Entry<K,V>>
would be great!

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 have HashMap 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 had entrySet(), 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 hit map. in IDE, I don't want to see .remove(Object obj), and .remove(Map.Entry<K,V> entry) because I can't do hit ctrl+space, r, return and be done with it.

甜警司 2024-09-05 06:56:39
  1. Java中的Map接口遵循键/值对结构,而Collection接口是对象的集合,这些对象以指定机制存储在结构中。
  2. Map没有扩展Collection接口的主要原因是Collection接口的add(E e)方法不支持像Map接口的put(k,v)那样的键值对代码>方法。
  3. 它可能不会扩展 Collection 接口,但仍然是 Java Collection 框架的一个组成部分
  1. The Map interface in Java follows key/value pair structure whereas the Collection interface is collection of objects which are stored in structure with a specified mechanism.
  2. The main reason map doesn't extend Collection interface is that add(E e) method of collection interface doesn't support key-value pair like map interface's put(k,v) method.
  3. It might not extend Collection interface but still an integral part of Java Collection framework
南…巷孤猫 2024-09-05 06:56:39

直接又简单。
Collection 是一个只需要一个对象的接口,而 Map 需要两个对象。

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

Straight and simple.
Collection is an interface which is expecting only one Object, whereas Map requires Two.

Collection(Object o);
Map<Object,Object>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文