为什么LinkedHashMap的keyset()方法返回Set而不是LinkedHashSet?

3
我在Java文档和这篇文章中读到,LinkedHashMap的keyset()保持顺序。我的问题是,如果它保证顺序,为什么LinkedHashMap的源代码不返回一个保证顺序的Set类型的对象,比如LinkedHashSet?我能想到的一个原因是,LinkedHashSet使用了一个map,这会增加内存分配(取决于AbstractSet的实现方式)。这也是因为它未来的实现可以支持keyset吗?就像这篇文章中所说的那样:Is it better to use List or Collection?

返回List符合编程到最高适当接口的原则。

返回Collection会对用户造成歧义,因为返回的集合可能是Set、List或Queue。

所以,如果没有阅读 keyset() 的文档,这不是模棱两可的吗? keyset() 源代码:
public Set<K> keySet() {
    Set<K> ks = keySet;
    return (ks != null ? ks : (keySet = new KeySet()));
}

private final class KeySet extends AbstractSet<K> {
    public Iterator<K> iterator() {
        return newKeyIterator();
    }
    public int size() {
        return size;
    }
    public boolean contains(Object o) {
        return containsKey(o);
    }
    public boolean remove(Object o) {
        return HashMap.this.removeEntryForKey(o) != null;
    }
    public void clear() {
        HashMap.this.clear();
    }
}
4个回答

6

为什么LinkedHashMap的源代码不返回一个保证有序的Set对象,就像LinkedHashSet一样?

因为LinkedHashSet是一个具体类,它拥有自己的实现来维护自己的数据,并且keyset()方法必须返回Map的视图,所以它不能只复制键数据到LinkedHashSet。请参见javadoc:

返回此映射中包含的键的Set视图。该集合由映射支持,因此对映射的更改会反映在集合中,反之亦然。


5
它返回一个由Map接口定义的Set。Set是一个只包含不重复元素的集合。另一方面,List是一个有序集合(也称为序列)。List接口不要求元素是唯一的。
然而,LinkedHashMap实现实际上返回底层的keySet,它是一个LinkedKeySet,并且LinkedKeySet的forEach()方法保留顺序,即:
//from the source code for the LinkedHashMap:
public Set<K> keySet() {
    Set<K> ks;
    return (ks = keySet) == null ? (keySet = new LinkedKeySet()) : ks;
}

因此,在这种情况下,元素既是唯一的又是有序的。

虽然你的第二部分是正确的,但第一部分并不一定正确。仅仅因为Map接口指定了Set,并不意味着它不能使用LinkedKeySet进行重写。 - Brandon Ling
我的观点是要强调LinkedKeySet在其元素上提供排序。我并不想暗示一个Set不能同时实现List接口。@Andreas的回答解决了你关于“为什么”的问题;而我试图解决的是“我们该怎么办”的一面。 - Austin

1
如果它能保证顺序,为什么LinkedHashMap的源代码没有返回一个保证顺序的Set对象,就像LinkedHashSet一样?键集合的声明类型来自于Map.keySet()声明的类型。在协变返回类型被添加到Java语言之前,LinkedHashMap被添加到标准库中。那时,没有使用Set类型的替代方法。无论如何,仅仅因为键集合必须与底层映射具有相同的顺序,并不意味着它必须是LinkedHashSet,实际上它不能是LinkedHashSet,因为它不能支持元素添加。它的类是LinkedHashMap类的私有嵌套类。在该类和Set之间没有任何公共类型,比Set作为返回类型更有用。

实际上,我认为定义一个这样的集合并不容易。有哪些基本特征可以将其与其他任何集合区分开来?你不能仅仅说“迭代顺序”,而不指定实例会有什么迭代顺序,而该顺序取决于从中获取集合的LinkedHashMap实例。换句话说,迭代顺序是实例特性,而不是类特性。因此,表达作为LinkedHashMap键集的本质的声明类型几乎提供不了任何实用信息,除了Set提供的信息。

无论如何,我并不觉得你引用的关于返回ListCollection之间的摘录很有说服力。事实上,对于对象特定类的歧义本质上并不是一个问题。在CollectionList的例子中,如果类型Collection很好地捕捉到了方法的返回类型要求,以至于没有特别需要List的特征,那么Collection就是一种很好的返回类型选择。尤其是如果这个需求也可以被Set满足,因为这样API就不会在两个同样好的选择之间做出本质上的任意选择。

有一种思想认为,方法应该尽可能地通用,以便在其参数类型方面尽可能具体。在它们的返回类型方面尽可能具体。我认为这个想法有很多优点,但它更像是一条指导方针而不是一条规则,并且在“尽可能具体”的具体程度上有很大的解释空间。


0

Java 21正在向LinkedHashMap添加一个sequencedKeySet方法,该方法将键集返回为SequencedSet(即具有明确定义的遍历顺序的Set)。虽然keySet方法仍然是一个Set以保持向后兼容性,但新的方法具有特定类型,可以确保具有明确定义的迭代顺序。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接