为什么Java Map不扩展Collection?

时间:2010-04-16 09:17:21

标签: java oop collections

我很惊讶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()),它就不会只需扩展该界面。

  

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

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

  

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

但如果是这种情况那么为什么接口指定了entrySet?它必须以某种方式有用(我认为这个位置很容易争论!)。

  

您无法询问给定键映射到的值,也不能删除给定键的条目而不知道它映射到的值。

我并不是说Map就是这样!它可以和 保留所有其他方法(entrySet除外,这现在是多余的)!

9 个答案:

答案 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()方法。因此,主要原因是数据存储在MapCollection中的方式不同。 此外,如果您回忆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>