Java Map为什么不扩展Collection?


146

事实Map<?,?>并非如此,这让我感到惊讶Collection<?>

我认为,如果这样声明,那将是很有意义的:

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

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

那么,为什么没有这样一个充分的理由实现它呢?


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

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

确实interface Map<K,V> extends Set<Map.Entry<K,V>>会很棒!

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

但是,如果是这种情况,那么为什么要entrySet由接口指定?它必须以某种方式有用(我认为很容易为该职位辩护!)。

您既不能询问给定键所映射的值,也不能在不知道给定键所映射的值的情况下删除给定键的条目。

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


1
事件Set <Map.Entry <K,V >>实际上
Maurice Perry 2010年

3
只是没有。它基于观点(如设计常见问题解答中所述),而不是合乎逻辑的结论。作为一种替代方法,请查看C ++ STL(sgi.com/tech/stl/table_of_contents.html)中的容器设计,该设计基于Stepanov的全面而出色的分析。
beldaz

Answers:


123

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

Map为什么不扩展Collection?

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

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

可以进行收集以扩展Map,但这引出了一个问题:密钥是什么?没有真正令人满意的答案,强迫一个导致不自然的界面。

可以将地图视为(键,值或对的)集合,这一事实反映在地图的三个“集合视图操作”(键集,入口集和值)中。虽然原则上可以将列表视为映射到元素的索引的列表,但它具有讨厌的属性,即从列表中删除元素会更改与删除的元素之前的每个元素关联的键。这就是为什么我们在列表上没有地图视图操作的原因。

更新:我认为报价可以回答大多数问题。值得强调的是有关条目集合的部分不是特别有用的抽象。例如:

Set<Map.Entry<String,String>>

将允许:

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

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

Map要求唯一的键,因此会违反此要求。或者,如果您在Set条目上加上唯一键,那么Set从一般意义上说,它并不是真正的键。这是一个Set进一步的限制。

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

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

entrySet()存在的主要原因是简化遍历,因此您不必遍历键,然后查找键。不要将其作为a Map应当Set为条目(imho)的初步证据。


1
此更新非常有说服力,但确实,by所返回的Set视图entrySet()支持remove,但不支持add(可能是throws UnsupportedException)。所以,我明白了你的意思,但是我也再次看到了OP的意思。(我的看法如我自己的回答所述。)
Enno Shioji

1
Zwei提出了一个很好的观点。由于这些复杂性,add可以像entrySet()视图一样处理:允许某些操作而不允许其他操作。当然,自然会回答“ Set不支持add哪种?” -好吧,如果这样的行为可以接受entrySet(),那为什么不接受this呢?这么说,我大多确信这是为什么没有这样一个伟大的想法已经为我曾经想过,但我仍然认为这是值得进一步讨论的,如果仅仅是为了丰富我自己的是什么造就了良好的API / OOP设计的理解。
polygenelubricants 2010年

3
常见问题解答中引用的第一段很有趣:“我们觉得……因此……毫无意义”。我不认为“感觉”和“感觉”会得出结论; o)
Peter Wippermann 2012年

可以进行扩展Map的集合吗?不确定,为什么作者考虑Collection扩展Map
overexchange's

11

虽然您已经获得了很多直接涵盖您问题的答案,但我认为退后一步,然后更笼统地看一下问题可能会很有用。也就是说,不要专门研究Java库是如何编写的,而是要研究编写Java库的原因。

这里的问题是,继承仅建模一种类型的公共性。如果您选择两个看起来都像“集合”的东西,则可能可以选择它们共同的8或10个东西。如果您选择另一对“收藏样”的东西,它们也会有8或10个共同点,但它们与第一对将不会是相同的 8或10个东西。

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

在这种情况下,继承(尤其是单继承)建模效果不佳。在哪些是真正的集合与哪些不是真正的集合之间没有明确的分界线,但是,如果您想定义一个有意义的Collection类,则将其中的一些留在外面。如果只保留其中的几个,则Collection类将只能提供相当稀疏的接口。如果您遗漏了更多内容,则可以为其提供更丰富的界面。

有些人还可以这样选择:“这种类型的集合支持操作X,但是您不能使用它,因为它是从定义X的基类派生的,但是尝试使用派生类的X会失败(例如,则引发异常)。

这仍然存在一个问题:几乎不管您遗漏了哪些内容,以及放了哪些内容,您都将不得不在哪些类别和哪些类别之间划清界限。无论您在哪条线上画线,都将在一些非常相似的事物之间进行清晰,人为的划分。


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 

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

更好的是主观的。


3

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


4
这是Set工作方式,并且Set 确实实现Collection
Lii 2015年

2

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

这种结构将允许一系列“集合”的交集,并集等。因此,层次结构应为:

                                Set

                                 |

                              Relation

                                 |

                                Map

                                / \

                             Bag Sequence

Sun / Oracle / Java ppl-请下次正确使用。谢谢。


4
我想详细了解这些虚构接口的API。不知道我想要一个键是整数的序列(又名列表);这听起来像是性能上的巨大打击。
劳伦斯·多尔

没错,序列是一个列表-因此Sequence扩展了List。我不知道您是否可以访问它,但这可能很有趣 portal.acm.org/citation.cfm?id=1838687.1838705
xagyg 2010年

@SoftwareMonkey这里是另一个链接。最后一个链接是指向API的javadoc的链接。zedlib.sourceforge.net
xagyg 2012年

@SoftwareMonkey我毫不掩饰地承认设计是我的主要关注点。我假设Oracle / Sun的专家可以对其进行优化。
xagyg 2012年

我倾向于不同意。如果您不能总是将域的非成员元素添加到集合中(如@cletus答案中的示例),那么如何保留“映射是一个集合”。
einpoklum 2015年

1

如果您查看各自的数据结构,则可以轻松猜测为什么Map不属于Collection。每个Collection存储单个值,其中Map存储键值对。因此,Collection接口中的方法与接口不兼容Map。例如Collection我们有add(Object o)。什么样的实现在Map。在中使用这种方法没有任何意义Map。相反,我们在中有一个put(key,value)方法Map

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

我们可以通过多种方法遍历a中的键和值存储,Map这就是Collection框架的一部分。


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.您可以显示一个代码示例吗?
RamenChef

很好地解释了这一点。我明白了 谢谢!
Emir Memic '17

一个实现add(Object o)是添加一个Entry<ClassA, ClassB>对象。一个Map可以考虑Collection of Tuples
利瓦诺夫

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(),没有人会忘记实现这个方法,你可以肯定,你可以得到对的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.Entry上设置的地图”,那么一个人会费心地执行相关的方法。如果不能做出这样的陈述,那么就不会做出-也就是说,这应该引起麻烦。
einpoklum 2015年

0

Map<K,V>不应扩展,Set<Map.Entry<K,V>>因为:

  • 不能Map.Entry具有相同键的s 添加到相同的Map,但是
  • 可以Map.Entry具有相同键的不同s 添加到相同的Set<Map.Entry>

...这是@cletus回答第二部分的精髓的简洁陈述。
einpoklum 2015年

-2

简洁明了。集合是仅期望一个对象的接口,而地图需要两个对象。

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

好的Trinad,您的答案总是简单而简短。
Sairam'3
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.