【问题标题】:Why do many Collection classes in Java extend the abstract class and implement the interface as well?为什么 Java 中的许多 Collection 类都扩展了抽象类并实现了接口?
【发布时间】:2011-04-20 18:20:23
【问题描述】:

为什么Java中的很多Collection类都扩展了Abstract类,也实现了接口(给定的抽象类也实现了)?

例如,HashSet 类扩展了AbstractSet,也实现了Set,但AbstractSet 已经实现了Set

【问题讨论】:

    标签: java collections


    【解决方案1】:

    从类型系统的角度来看,如果类不再实现接口,它们不会有任何不同,因为抽象基类已经实现了它们。

    这是真的。

    无论如何,他们实现它的原因(可能)主要是文档:HashSet is-a Set。这通过在末尾添加 implements Set 来明确说明,尽管这不是绝对必要的。

    请注意,使用反射实际上可以观察到差异,但如果HashSet 没有直接实现Set,我将很难生成一些会中断的代码。

    【讨论】:

    • +1,例如 getInterfaces() : ideone.com/BDpdr 但是使用这种方法会很危险,因为它会提供所有实现的接口。
    【解决方案2】:

    这在实践中可能无关紧要,但我想澄清的是,显式实现接口与通过继承实现接口并不完全相同。差异存在于编译的类文件中,并且通过反射可见。例如,

    for (Class<?> c : ArrayList.class.getInterfaces())
        System.out.println(c);
    

    输出仅显示由ArrayList 实现的接口显式,按照它们在源代码中编写的顺序,[在我的Java版本上]是:

    interface java.util.List
    interface java.util.RandomAccess
    interface java.lang.Cloneable
    interface java.io.Serializable
    

    输出不包括由超类实现的接口,或作为所包含接口的超接口的接口。特别是,上面缺少IterableCollection,尽管ArrayList 隐式实现了它们。要找到它们,您必须递归地迭代类层次结构。

    如果某些代码使用反射并且依赖于显式实现的接口,那将是不幸的,但这是可能的,因此集合库的维护者现在可能不愿意更改它,即使他们愿意。 (有一个名为Hyrum's Law 的观察:“如果有足够数量的 API 用户,你在合同中承诺的内容并不重要;系统的所有可观察行为都将取决于某人”。)

    幸运的是,这种差异不会影响类型系统。表达式 new ArrayList&lt;&gt;() instanceof IterableIterable.class.isAssignableFrom(ArrayList.class) 仍然计算为 true

    【讨论】:

      【解决方案3】:

      来自 Joshua Bloch 的“Effective Java”:

      您可以通过添加抽象骨架实现类与接口一起使用来结合接口和抽象类的优点。

      接口定义类型,可能提供一些默认方法,而骨架类在原始接口方法之上实现剩余的非原始接口方法。扩展一个骨架实现需要完成接口的大部分工作。这是Template Method 模式。

      按照惯例,skeletal 实现类称为AbstractInterface,其中Interface 是它们实现的接口的名称。例如:

      AbstractCollection
      AbstractSet
      AbstractList
      AbstractMap
      

      【讨论】:

        【解决方案4】:

        回答太晚了?

        我正在猜测以验证我的答案。假设以下代码

        HashMap extends AbstractMap(未实现 Map)

        AbstractMap implements Map

        现在想象一下,某个随机的家伙来了,将 Map 更改为一些 java.util.Map1,其方法集与 Map 完全相同

        在这种情况下,不会出现任何编译错误,并且 jdk 会被编译(当然测试会失败并捕获它)。

        现在任何使用 HashMap 作为 Map m= new HashMap() 的客户端都将开始失败。这很下游。

        由于 AbstractMap、Map 等都来自同一个产品,因此这个论点显得幼稚(很可能是。也可能不是。),但想想基类来自不同 jar/第三方库的项目等等。然后第三方/不同的团队可以改变他们的基本实现。

        通过在 Child 类中实现“接口”,开发人员也尝试使该类自给自足,防止 API 损坏。

        【讨论】:

        • "在这种情况下不会有任何编译错误" ....等等...你认为整个JDK不使用HashMap作为Map&lt;...&gt; m= new HashMap&lt;&gt;()?
        • @Tom 我说的是一种模式。 jdk足够大,可以使用,如果API较小,则可能无法使用。但是当我们发布一个 API 时,它必须是自给自足的,这是我“猜测”的。除此之外,可读性和所有其他原因已经列出
        【解决方案5】:

        在我看来,当一个类实现一个接口时,它必须实现其中存在的所有方法(默认情况下,它们是接口中的公共和抽象方法)。

        如果我们不想实现接口的所有方法,它必须是一个抽象类。

        因此,如果某些方法已经在某个抽象类中实现了特定接口,并且我们必须为其他未实现的方法扩展功能,我们将需要在我们的类中再次实现原始接口以获取剩余的方法集.它有助于维护接口制定的合同规则。

        如果只实现接口并再次用我们类中的方法定义覆盖所有方法,将导致返工。

        【讨论】:

        • 虽然这可能是真的(我想,这对我来说很难理解),但它并没有真正回答这个问题,这就是为什么有些类实现了它们的超类也实现的接口,而它们却不会实现不必。
        • “我们将需要在我们的类中再次实现原始接口以获取剩余的方法集”。那没有必要。如果抽象类只实现了接口的部分方法,则非抽象子类有义务实现其余部分,但该子类仍然没有必要明确表示它实现了接口。跨度>
        【解决方案6】:

        我也认为这是为了清楚起见。 Java Collections 框架具有相当多的接口层次结构,定义了不同类型的集合。它从 Collection 接口开始,然后由三个主要子接口 Set、List 和 Queue 扩展。还有SortedSet扩展Set和BlockingQueue扩展Queue。

        现在,实现它们的具体类更容易理解,如果它们明确说明它正在实现的层次结构中的哪个接口,即使它有时看起来是多余的。正如您所提到的,像 HashSet 这样的类实现了 Set,但像 TreeSet 这样的类虽然也扩展了 AbstractSet 实现了 SortedSet,但它比 Set 更具体。 HashSet 可能看起来是多余的,但 TreeSet 不是因为它需要实现 SortedSet。尽管如此,这两个类都是具体的实现,如果它们在声明中都遵循一定的约定,就会更容易理解。

        甚至还有实现不止一种集合类型的类,比如实现 List 和 Queue 的 LinkedList。但是,至少有一类有点“非常规”,即 PriorityQueue。它扩展了 AbstractQueue 但没有显式地实现 Queue。不要问我为什么。 :)

        (参考来自 Java 5 API)

        【讨论】:

          【解决方案7】:

          我想可能有一种不同的方式来处理集合的成员,即接口,即使提供默认操作实现并不作为一刀切。循环队列与 LIFO 队列可能都实现了相同的接口,但它们的具体操作会有所不同,对吧?

          【讨论】:

            【解决方案8】:

            Colin Hebert 不同,我不相信那些关注可读性的写作者。 (任何认为标准 Java 库是由无懈可击的上帝编写的人,都应该看看他们的源代码。第一次这样做时,我被代码格式和大量复制粘贴的块吓坏了。)

            我敢打赌,已经很晚了,他们累了,也不在乎。

            【讨论】:

            • 虽然很多默认库类的源代码是一团糟,但这些类的公共接口通常比实现要好很多(当然也有一些例外,比如整个Date /Calendar混乱)。
            • @Joachim 也许吧。另一方面,这不会改变公共接口。例如,当你用笔和纸设计整个库时,你不会为了“可读性”在 HashSetSet 之间画出额外的线。所以,它仍然是一个实现选择。
            【解决方案9】:

            如果你只有一个抽象类,你就不能创建一个你自己的类,它也继承自另一个类。

            【讨论】:

            • 我认为问题是为什么具体实现再次显式实现接口,因为它已经被抽象基类“实现”了。
            【解决方案10】:

            这是一种记住此类确实实现了该接口的方法。
            它不会产生任何不良影响,并且可以帮助理解代码,而无需遍历给定类的完整层次结构。

            【讨论】:

            • +1 此外,AbstractSet 的维护者可能会决定更改层次结构并删除 Set 以供将来发布(显然不会发生,但可能会发生在其他非核心类中)。在这种情况下,HashSet 的编译将失败,而不是所有使用 HashSet 作为 Set 的类。
            • 那么为什么不放一个 Collection 接口呢?这是愚蠢和不必要的。
            猜你喜欢
            • 2010-10-21
            • 1970-01-01
            • 1970-01-01
            • 2017-03-14
            • 2012-10-08
            • 1970-01-01
            • 1970-01-01
            • 2013-05-20
            • 2011-09-16
            相关资源
            最近更新 更多