为什么Java中的许多Collection类扩展了Abstract类并实现了接口(也是由给定的抽象类实现的)?
例如,类HashSet
扩展AbstractSet
并实现Set
,但AbstractSet
已实现Set
。
答案 0 :(得分:32)
这是一种记住这个类真正实现该接口的方法 它不会产生任何不良影响,它可以帮助理解代码,而无需通过给定类的完整层次结构。
答案 1 :(得分:11)
从类型系统的角度来看,如果类没有再次实现接口,则类将不会有任何不同,因为抽象基类已经实现了它们。
那是真的。
他们确实实施它的原因(可能)主要是文档:HashSet
是一个Set
。通过在末尾添加implements Set
来明确说明这一点,尽管并非绝对必要。
请注意,使用反射实际上可以观察到差异,但是如果HashSet
没有直接实现Set
,我会很难产生一些会破坏的代码。
答案 2 :(得分:9)
这在实践中可能无关紧要,但我想澄清一下,明确实现一个接口与完全相同,就像通过继承实现它一样。不同之处在于已编译的类文件,并且通过反射可见。如,
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用户,那么您在合同中承诺的并不重要;系统的所有可观察行为都将取决于某人”。)< / p>
幸运的是,这种差异不会影响类型系统。表达式new ArrayList<>() instanceof Iterable
和Iterable.class.isAssignableFrom(ArrayList.class)
仍然评估为true
。
答案 3 :(得分:5)
与 Colin Hebert 不同,我不会购买那些关心可读性的人。 (每个认为标准Java库都是由无可挑剔的神创造的人,应该把它看作是它们的来源。我第一次这样做时,我对代码格式化和大量复制粘贴块感到震惊。)
我的赌注是迟到了,他们累了,也不在乎。
答案 4 :(得分:2)
您可以通过添加抽象骨架实现类来结合接口和抽象类的优点来与接口结合使用。
接口定义类型,可能提供一些默认方法,而骨架类在基本接口方法上实现剩余的非基本接口方法。扩展骨架实现需要完成大部分工作来实现接口。这是Template Method模式。
按照惯例,骨架实现类称为AbstractInterface
,其中Interface
是它们实现的接口的名称。例如:
AbstractCollection
AbstractSet
AbstractList
AbstractMap
答案 5 :(得分:1)
我也相信这是为了清晰。 Java Collections框架具有相当的接口层次结构,用于定义不同类型的集合。它从Collection接口开始,然后由三个主要子接口Set,List和Queue扩展。还有SortedSet扩展Set和BlockingQueue扩展队列。
现在,实现它们的具体类更容易理解,如果它们明确说明它正在实现的层次结构中的哪个接口,即使它有时看起来多余。正如您所提到的,像HashSet这样的类实现了Set但是像TreeSet这样的类,虽然它也扩展了AbstractSet实现的SortedSet,而不仅仅是Set。 HashSet可能看起来多余,但TreeSet不是因为它需要实现SortedSet。尽管如此,这两个类都是具体的实现,如果两个类都遵循其声明中的某些约定,那么它们将更容易理解。
甚至有一些类实现了多个集合类型,比如LinkedList,它实现了List和Queue。但是,有一个类至少有点'非常规',即PriorityQueue。它扩展了AbstractQueue但没有明确地实现Queue。不要问我为什么。 :)
(引用来自Java 5 API)
答案 6 :(得分:0)
在我看来,当一个类实现一个接口时,它必须实现它中存在的所有方法(因为默认情况下它们是接口中的公共和抽象方法)。
如果我们不想实现接口的所有方法,它必须是一个抽象类。
所以这里如果某些方法已经在一些实现特定接口的抽象类中实现,并且我们必须为未实现的其他方法扩展功能,我们将需要再次在我们的类中实现原始接口以获取剩余的方法集。它有助于维护界面规定的合同规则。
如果只实现接口并再次使用我们类中的方法定义覆盖所有方法,将导致返工。
答案 7 :(得分:0)
回答太晚了?
我正在猜测验证我的答案。假设以下代码
HashMap extends AbstractMap
(未实施地图)
AbstractMap implements Map
现在想象一下随机的人来了,Changed将Map实现到一些java.util.Map1,其方法与Map
完全相同在这种情况下,不会有任何编译错误并且jdk被编译(当然测试失败并捕获它)。
现在任何使用HashMap作为Map m = new HashMap()的客户端都会失败。这是下游。
由于AbstractMap,Map等都来自同一产品,因此这个参数看起来很幼稚(很可能是。或者可能不是。),但想想一个基类来自不同jar /第三方库的项目然后第三方/不同的团队可以改变他们的基础实施。
通过在Child类中实现“接口”,开发人员也尝试使类自足,API破坏证明。
答案 8 :(得分:-1)
我认为可能有一种不同的方式来处理集合的成员,即接口,即使提供默认操作实现也不是一个通用的。循环队列与LIFO队列可能都实现相同的接口,但它们的具体操作将以不同的方式实现,对吧?
答案 9 :(得分:-3)
如果你只有一个抽象类,那么你也无法创建一个继承自另一个类的类。