代码之家  ›  专栏  ›  技术社区  ›  polygenelubricants

为什么JavaMap没有扩展集合?

  •  131
  • polygenelubricants  · 技术社区  · 15 年前

    我很惊讶 Map<?,?> 不是一个 Collection<?> .

    我想如果是这样宣布的话会很有意义:

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

    毕竟,A Map<K,V> Map.Entry<K,V> 不是吗?

    那么,有没有一个很好的理由说明为什么没有这样实施呢?


    感谢克莱特斯最权威的回答,但我仍然想知道为什么,如果你已经可以看到 地图<k,v> 作为 Set<Map.Entries<K,V>> (通过 entrySet() ,它不仅扩展了接口。

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

    确切地, interface Map<K,V> extends Set<Map.Entry<K,V>> 太好了!

    但这提供了一个非常有限的(而且不是特别有用的) 地图 抽象化。

    但如果是这样的话为什么 entrySet 由接口指定?它一定是有用的(我认为很容易为这个职位辩护!).

    不能询问给定密钥映射到哪个值,也不能在不知道它映射到哪个值的情况下删除给定密钥的条目。

    我不是说就这些 地图 !它可以和 应该 保留所有其他方法(除了 入口集 ,现在是多余的)!

    9 回复  |  直到 6 年前
        1
  •  115
  •   Magnilex BesaFX    11 年前

    Java Collections API Design FAQ :

    为什么map不扩展collection?

    这是故意的。我们觉得 映射不是集合和 集合不是映射。因此,它 地图的扩展没有什么意义 集合接口(或vice 反之亦然)。

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

    收藏可以扩展 地图,但这提出了一个问题: 钥匙是什么?真的没有 令人满意的回答 导致不自然的界面。

    地图可以看作是 键、值或对),以及这个事实 体现在三个“收藏” 查看地图上的“操作”(键集, entryset和values)。当它是,在 原则,可以将列表视为 将索引映射到元素的映射, 这有一个令人讨厌的特性 从列表中删除元素 更改与每个 删除元素之前的元素。 所以我们没有地图视图 列表操作。

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

    Set<Map.Entry<String,String>>
    

    允许:

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

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

    Map s需要唯一的密钥,因此这会违反这一点。或者如果在 Set 在条目中,它不是真正的 集合 一般来说。这是一个 集合 还有进一步的限制。

    可以说你可以说 equals() / hashCode() 关系 地图入口 纯粹是关键问题,但即便如此也有问题。更重要的是,它真的增加了价值吗?你可能会发现,一旦你开始看角落的案例,这个抽象就崩溃了。

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

    主要原因 entrySet() 存在就是简化遍历,这样就不必遍历键,然后查找键。不要把它当作 地图 应该是 集合 条目(imho)。

        2
  •  10
  •   L. Cornelius Dol    12 年前

    我猜 为什么? 是主观的。

    在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
  •  9
  •   Jerry Coffin    15 年前

    虽然你已经得到了很多答案,相当直接地涵盖了你的问题,但我认为退一步,更一般地看待这个问题可能是有用的。也就是说,不要特别关注Java库是如何编写的,看看它为什么这样写。

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

    如果你看一打左右不同的“收藏类”东西,几乎每一个都可能有8到10个共同的特征,至少有一个是相同的,但是如果你看一下 每一个 其中一个,你几乎什么都没有。

    这是一种继承(特别是单继承)建模不好的情况。在哪些是真正的集合,哪些不是集合之间没有明确的界限——但是如果你想定义一个有意义的集合类,你就不得不把其中的一些排除在外。如果只保留其中的一些,那么collection类将只能提供相当稀疏的接口。如果你漏掉更多,你就能给它一个更丰富的界面。

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

    这仍然留下了一个问题:几乎不管你漏掉了哪些内容,也不管你放了哪些内容,你都必须在哪些类在哪些类在哪些类在哪些类之间划出一条硬线。不管你在哪里画那条线,你都会在一些东西之间留下一个清晰的,相当人为的划分 相当地 类似的。

        4
  •  3
  •   Mnementh    15 年前

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

        5
  •  2
  •   xagyg    15 年前

    Java集合被破坏。缺少一个接口,即关系接口。因此,映射扩展关系扩展集。关系(也称为多映射)具有唯一的名称-值对。映射(也称为“函数”),有唯一的名称(或键),当然映射到值。序列扩展映射(其中每个键都是整数>0)。行李(或多组)扩展地图(其中每个键是一个元素,每个值是元素在行李中出现的次数)。

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

                                    Set
    
                                     |
    
                                  Relation
    
                                     |
    
                                    Map
    
                                    / \
    
                                 Bag Sequence
    

    Sun / Oracle / Java PPL -请下次正确。谢谢。

        6
  •  1
  •   RamenChef    9 年前

    如果你看一下各自的数据结构,你很容易就能猜出为什么 Map 不属于 Collection . 各 收藏 存储单个值,其中 地图 存储键值对。SO方法 收藏 接口不兼容 地图 接口。例如 收藏 我们有 add(Object o) . 这样的实施在哪 地图 . 有这样的方法 地图 . 相反,我们有一个 put(key,value) 方法在 地图 .

    同样的论点也适用 addAll() , remove() removeAll() 方法。所以主要原因是数据存储方式的不同 地图 收藏 . 如果你还记得 收藏 实现的接口 Iterable 接口,即与 .iterator() 方法应返回迭代器,该迭代器必须允许我们对存储在 收藏 . 这样的方法对于 地图 ?键迭代器还是值迭代器?这也没有道理。

    有一些方法可以迭代存储在 地图 这就是它的一部分 收藏 框架。

        7
  •  0
  •   Enno Shioji    15 年前

    确切地, 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 把它干掉。

        8
  •  0
  •   einpoklum    10 年前

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

    • 不能 添加不同的 Map.Entry 同一把钥匙 Map 但是
    • 可以 添加不同的 地图入口 同一把钥匙 Set<Map.Entry> .
        9
  •  -1
  •   Pang Ajmal PraveeN    6 年前

    直截了当。 集合是一个只需要一个对象的接口,而映射需要两个对象。

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