为什么 Java Map 不扩展 Collection?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2651819/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 10:21:59  来源:igfitidea点击:

Why doesn't Java Map extend Collection?

javaoopcollections

提问by polygenelubricants

I was surprised by the fact that Map<?,?>is not a Collection<?>.

我对Map<?,?>不是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?

毕竟, aMap<K,V>是 的集合Map.Entry<K,V>,不是吗?

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.

感谢 Cletus 提供最权威的答案,但我仍然想知道为什么,如果您已经可以将 aMap<K,V>视为Set<Map.Entries<K,V>>(via entrySet()),它不仅扩展了该接口。

If a Mapis a Collection, what are the elements? The only reasonable answer is "Key-value pairs"

如果 aMap是 a Collection,元素是什么?唯一合理的答案是“键值对”

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

没错,interface Map<K,V> extends Set<Map.Entry<K,V>>会很棒!

but this provides a very limited (and not particularly useful) Mapabstraction.

但这提供了一个非常有限(并且不是特别有用)的Map抽象。

But if that's the case then why is entrySetspecified by the interface? It must be useful somehow (and I think it's easy to argue for that position!).

但如果是这样,那么为什么entrySet由接口指定?它必须以某种方式有用(我认为很容易为那个立场争论!)。

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 shouldkeep all the other methods (except entrySet, which is redundant now)!

我并不是说这就是它的全部内容Map!它可以而且应该保留所有其他方法(除了entrySet,现在是多余的)!

采纳答案by cletus

From the Java Collections API Design FAQ:

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

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.

为什么 Map 不扩展 Collection?

这是设计使然。我们觉得映射不是集合,集合也不是映射。因此,Map 扩展 Collection 接口几乎没有意义(反之亦然)。

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

可以使集合扩展 Map,但这提出了一个问题:键是什么?没有真正令人满意的答案,强迫一个会导致不自然的界面。

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

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.Entryinstance)

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

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

Maps 需要唯一的键,所以这会违反这一点。或者,如果您在 a Setof 条目上强加唯一键,则它实际上并不是Set一般意义上的 a。这是一个Set进一步的限制。

Arguably you could say the equals()/hashCode()relationship for Map.Entrywas 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.

可以说你可以说equals()/hashCode()关系Map.Entry纯粹是关键,但即使这样也有问题。更重要的是,它真的增加了任何价值吗?一旦您开始查看极端情况,您可能会发现这种抽象被打破了。

It's worth noting that the HashSetis actually implemented as a HashMap, not the other way around. This is purely an implementation detail but is interesting nonetheless.

值得注意的HashSet是,实际上是作为 a 实现的HashMap,而不是相反。这纯粹是一个实现细节,但仍然很有趣。

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 Mapshould be a Setof entries (imho).

entrySet()存在的主要原因是为了简化遍历,因此您不必遍历键然后查找键。不要将其视为 aMap应该是Set条目a的初步证据(恕我直言)。

回答by Mnementh

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.

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

回答by Enno Shioji

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

没错,interface Map<K,V> extends Set<Map.Entry<K,V>>会很棒!

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...

实际上,如果是这样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,而如果你希望你不是实现者已经实现了两个接口......

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, returnand be done with it.

我不想拥有的原因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

回答by ewernli

I guess the whyis subjective.

我想原因是主观的。

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

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

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:

在 Pharo Smalltak 中也是如此:

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":

但是某些方法存在不对称性。例如,collect:will 获取关联(相当于一个条目),而do:获取值。它们提供了另一种keysAndValuesDo:按条目迭代字典的方法。Add:接受关联,但remove:已被“抑制”:

remove: anObject
self shouldNotImplement 

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

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

What is better is subjective.

什么更好是主观的。

回答by Jerry Coffin

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.

虽然您已经获得了许多相当直接地涵盖了您的问题的答案,但我认为退一步并更广泛地看待问题可能会有所帮助。也就是说,不要专门查看 Java 库是如何编写的,而要查看它为什么是这样编写的。

The problem here is that inheritance only models onetype 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 same8 or 10 things as the first pair.

这里的问题是继承只为一种类型的共性建模。如果你挑出两件看起来都“像收藏”的东西,你大概可以挑出 8 到 10 件它们的共同点。如果你挑选出一对不同的“类似收藏”的东西,它们也会有 8 或 10 件共同点——但它们不会与第一对相同的8 或 10 件东西。

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 everyone of them, you're left with practically nothing.

如果你看一下有十几个不同的“集合式”的东西,实际上他们每个人都可能有这样的事情与至少一个其他常见的8个或10个特点-但如果你看看什么是跨共享的一个其中,您几乎一无所有。

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.

这是继承(尤其是单继承)不能很好地建模的情况。哪些是真正的集合,哪些不是——但如果你想定义一个有意义的 Collection 类,你就不得不将其中的一些排除在外。如果你只留下其中的几个,你的 Collection 类将只能提供一个相当稀疏的接口。如果您省略更多内容,您将能够为其提供更丰富的界面。

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).

有些人还选择基本上说:“这种类型的集合支持操作 X,但不允许您使用它,通过从定义 X 的基类派生,但尝试使用派生类 X 失败(例如,通过抛出异常)。

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 quitesimilar.

这仍然留下了一个问题:几乎不管你漏掉了哪些,你都必须在哪些课程和哪些课程之间划清界限。无论你在哪里画这条线,你都会在一些非常相似的东西之间留下一个清晰的、相当人为的划分。

回答by xagyg

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).

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

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.

Sun/Oracle/Java ppl - 请下次做对。谢谢。

回答by einpoklum

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

Map<K,V>不应延长,Set<Map.Entry<K,V>>因为:

  • You can'tadd different Map.Entrys with the same key to the same Map, but
  • You canadd different Map.Entrys with the same key to the same Set<Map.Entry>.
  • 不能Map.Entry具有相同键的不同s添加到相同的Map,但是
  • 可以Map.Entry具有相同键的不同s添加到相同的Set<Map.Entry>.

回答by Mayur Ingle

If you look at the respective data structure you can easily guess why Mapis not a part of Collection. Each Collectionstores a single value where as a Mapstores key-value pair. So methods in Collectioninterface are incompatible for Mapinterface. For example in Collectionwe 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.

如果您查看各自的数据结构,您很容易猜到为什么Map不是Collection. 每个Collection存储单个值,其中一个Map存储键值对。所以Collection接口中的方法与接口不兼容Map。例如在Collection我们有add(Object o). 在Map. 在Map. 相反,我们put(key,value)Map.

Same argument goes for addAll(), remove(), and removeAll()methods. So the main reason is the difference in the way data is stored in Mapand Collection. Also if you recall Collectioninterface implemented Iterableinterface 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.

同样的道理也适用于addAll()remove()removeAll()方法。所以主要原因是数据在Map和 中的存储方式不同Collection。此外,如果你回忆一下Collection接口实现的Iterable接口,即任何带有.iterator()方法的接口都应该返回一个迭代器,它必须允许我们迭代存储在Collection. 现在这种方法会为 a 返回Map什么?键迭代器还是值迭代器?这也没有道理。

There are ways in which we can iterate over keys and values stores in a Mapand that is how it is a part of Collectionframework.

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

回答by Trinadh Koya

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

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

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