【发布时间】:2011-04-20 18:20:23
【问题描述】:
为什么Java中的很多Collection类都扩展了Abstract类,也实现了接口(给定的抽象类也实现了)?
例如,HashSet 类扩展了AbstractSet,也实现了Set,但AbstractSet 已经实现了Set。
【问题讨论】:
标签: java collections
为什么Java中的很多Collection类都扩展了Abstract类,也实现了接口(给定的抽象类也实现了)?
例如,HashSet 类扩展了AbstractSet,也实现了Set,但AbstractSet 已经实现了Set。
【问题讨论】:
标签: java collections
从类型系统的角度来看,如果类不再实现接口,它们不会有任何不同,因为抽象基类已经实现了它们。
这是真的。
无论如何,他们实现它的原因(可能)主要是文档:HashSet is-a Set。这通过在末尾添加 implements Set 来明确说明,尽管这不是绝对必要的。
请注意,使用反射实际上可以观察到差异,但如果HashSet 没有直接实现Set,我将很难生成一些会中断的代码。
【讨论】:
getInterfaces() : ideone.com/BDpdr 但是使用这种方法会很危险,因为它会提供所有实现的接口。
这在实践中可能无关紧要,但我想澄清的是,显式实现接口与通过继承实现接口并不完全相同。差异存在于编译的类文件中,并且通过反射可见。例如,
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
输出不包括由超类实现的接口,或作为所包含接口的超接口的接口。特别是,上面缺少Iterable 和Collection,尽管ArrayList 隐式实现了它们。要找到它们,您必须递归地迭代类层次结构。
如果某些代码使用反射并且依赖于显式实现的接口,那将是不幸的,但这是可能的,因此集合库的维护者现在可能不愿意更改它,即使他们愿意。 (有一个名为Hyrum's Law 的观察:“如果有足够数量的 API 用户,你在合同中承诺的内容并不重要;系统的所有可观察行为都将取决于某人”。)
幸运的是,这种差异不会影响类型系统。表达式 new ArrayList<>() instanceof Iterable 和 Iterable.class.isAssignableFrom(ArrayList.class) 仍然计算为 true。
【讨论】:
您可以通过添加抽象骨架实现类与接口一起使用来结合接口和抽象类的优点。
接口定义类型,可能提供一些默认方法,而骨架类在原始接口方法之上实现剩余的非原始接口方法。扩展一个骨架实现需要完成接口的大部分工作。这是Template Method 模式。
按照惯例,skeletal 实现类称为AbstractInterface,其中Interface 是它们实现的接口的名称。例如:
AbstractCollection
AbstractSet
AbstractList
AbstractMap
【讨论】:
回答太晚了?
我正在猜测以验证我的答案。假设以下代码
HashMap extends AbstractMap(未实现 Map)
AbstractMap implements Map
现在想象一下,某个随机的家伙来了,将 Map 更改为一些 java.util.Map1,其方法集与 Map 完全相同
在这种情况下,不会出现任何编译错误,并且 jdk 会被编译(当然测试会失败并捕获它)。
现在任何使用 HashMap 作为 Map m= new HashMap() 的客户端都将开始失败。这很下游。
由于 AbstractMap、Map 等都来自同一个产品,因此这个论点显得幼稚(很可能是。也可能不是。),但想想基类来自不同 jar/第三方库的项目等等。然后第三方/不同的团队可以改变他们的基本实现。
通过在 Child 类中实现“接口”,开发人员也尝试使该类自给自足,防止 API 损坏。
【讨论】:
HashMap作为Map<...> m= new HashMap<>()?
在我看来,当一个类实现一个接口时,它必须实现其中存在的所有方法(默认情况下,它们是接口中的公共和抽象方法)。
如果我们不想实现接口的所有方法,它必须是一个抽象类。
因此,如果某些方法已经在某个抽象类中实现了特定接口,并且我们必须为其他未实现的方法扩展功能,我们将需要在我们的类中再次实现原始接口以获取剩余的方法集.它有助于维护接口制定的合同规则。
如果只实现接口并再次用我们类中的方法定义覆盖所有方法,将导致返工。
【讨论】:
我也认为这是为了清楚起见。 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)
【讨论】:
我想可能有一种不同的方式来处理集合的成员,即接口,即使提供默认操作实现并不作为一刀切。循环队列与 LIFO 队列可能都实现了相同的接口,但它们的具体操作会有所不同,对吧?
【讨论】:
与 Colin Hebert 不同,我不相信那些关注可读性的写作者。 (任何认为标准 Java 库是由无懈可击的上帝编写的人,都应该看看他们的源代码。第一次这样做时,我被代码格式和大量复制粘贴的块吓坏了。)
我敢打赌,已经很晚了,他们累了,也不在乎。
【讨论】:
Date /Calendar混乱)。
如果你只有一个抽象类,你就不能创建一个你自己的类,它也继承自另一个类。
【讨论】:
这是一种记住此类确实实现了该接口的方法。
它不会产生任何不良影响,并且可以帮助理解代码,而无需遍历给定类的完整层次结构。
【讨论】: