inheritance,组合和默认方法
通常承认通过inheritance扩展接口的实现不是最佳实践,并且该组合(例如,从头开始再次实现接口)更加可维护。
这是有效的,因为接口契约强制用户实现所有所需的function。 但是在java 8中,默认方法提供了一些可以“手动”覆盖的默认行为。 请考虑以下示例:我想设计一个用户数据库,该数据库必须具有List的function。 出于效率目的,我选择通过ArrayList来支持它。
public class UserDatabase extends ArrayList{}
这通常不被认为是一种很好的做法,如果实际上希望列表具有完整的function并遵循通常的“合成而不是inheritance”的座右铭,那么人们更愿意:
public class UserDatabase implements List{ //implementation here, using an ArrayList type field, or decorator pattern, etc. }
但是,如果不注意,则不需要覆盖某些方法,例如spliterator(),因为它们是List接口的默认方法。 问题是,List的spliterator()方法执行得比ArrayList的spliterator()方法差得多,后者已针对ArrayList的特定结构进行了优化。
这迫使开发人员
- 请注意,ArrayList有自己的,更有效的spliterator()实现,并手动覆盖他自己的List实现的spliterator()方法或者
- 使用默认方法会失去大量性能。
所以问题是:在这种情况下,人们应该更喜欢构成而不是inheritance吗?
在开始考虑性能之前,我们总是应该考虑正确性,即在您的问题中我们应该考虑使用inheritance而不是委托所暗示的内容。 这个EclipseLink / JPA问题已经说明了这一点 。 由于inheritance,如果尚未填充延迟填充列表,则排序(同样适用于流操作)不起作用。
因此,我们必须在专业化,覆盖新的default
方法,在inheritance情况下完全中断以及default
方法不能在委托情况下以最大性能工作的可能性之间进行权衡。 我想,答案应该是显而易见的。
由于您的问题是关于新的default
方法是否会改变这种情况,因此应该强调的是,与以前甚至不存在的方法相比,您所谈论的是性能下降。 让我们留在sort
榜样。 如果使用委托并且不覆盖default
排序方法,则default
方法的性能可能低于优化的ArrayList.sort
方法,但在Java 8之前,后者不存在,并且未针对ArrayList
优化的算法是标准行为。
因此,当您不覆盖default
方法时,您不会在Java 8下失去与委托的性能,您只是没有获得更多。 我认为,由于其他改进,性能仍然优于Java 7(没有default
方法)。
Stream
API不容易比较,因为在Java 8之前不存在API。但是,很明显类似的操作,例如,如果你手动实现减少,除了通过你的委托列表的Iterator
之外别无选择。要防止remove()
尝试,因此包装ArrayList
Iterator
,或使用size()
和get(int)
委托给支持List
。 因此,没有预先default
方法API可以表现出比Java 8 API的default
方法更好的性能的default
,因为过去没有ArrayList
特定的优化。
也就是说,您可以通过以不同方式使用组合来改进您的API设计:不要让UserDatabase
实现List
。 只需通过访问方法提供List
。 然后,其他代码不会尝试流式传输UserDatabase
实例,而是覆盖访问器方法返回的列表。 返回的列表可以是只读包装器 ,它提供JRE本身提供的最佳性能,并在可行的default
小心覆盖default
方法。
我真的不明白这里的大问题。 即使没有扩展, 也可以使用ArrayList备份UserDatabase
, 并通过委派获得性能。 您无需对其进行扩展即可获得性能。
public class UserDatabase implements List{ private ArrayList list = new ArrayList (); // implementation ... // delegate public Spliterator() spliterator() { return list.spliterator(); } }
你的两点并没有改变这一点。 如果你知道“ArrayList有自己的,更有效的spliterator()实现”,那么你可以将它委托给你的后台实例,如果你不知道,那么默认方法会处理它。
我仍然不确定实现List接口是否真的有意义,除非您明确地创建了一个可重用的Collection库。 更好地为这样的一次性创建自己的API,通过inheritance(或接口)链不会带来未来的问题。
我不能为每种情况提供建议,但对于这种特殊情况,我建议不要实施List
。 UserDatabase.set(int, User)
的目的是什么? 您真的想用全新的用户替换支持数据库中的第i个条目吗? 那么add(int, User)
呢? 在我看来,您应该将其实现为只读列表(在每个修改请求上抛出UnsupportedOperationException
)或仅支持一些修改方法(如add(User)
支持,但add(int, User)
不支持)。 但后一种情况会让用户感到困惑。 最好提供更适合您任务的修改API。 至于读取请求,可能最好返回一个用户流:
我建议创建一个返回Stream
的方法:
public class UserDatabase { List list = new ArrayList<>(); public Stream users() { return list.stream(); } }
请注意,在这种情况下,您将来可以完全自由地更改实施。 例如,将ArrayList
替换为TreeSet
或ConcurrentLinkedDeque
或其他。
选择很简单,根据您的要求。
注 – 以下只是一个用例。 来说明差异。
如果你想要一个不会保留重复的列表并且要做一大堆与ArrayList
截然不同的事情,那么就没有使用扩展ArrayList
因为你是从头开始编写所有东西。
在上面你应该Implement List
。 但是,如果您只是优化ArrayList
的实现,那么您应该复制粘贴整个ArrayList
实现并遵循优化而不是extending ArrayList
。 为什么,因为多层次的实施使得某人试图解决问题变得困难。
例如:一台4GB Ram作为父母的计算机,而Child正在使用8 GB RAM。 如果父级有4 GB而新的Child有4 GB可以制作8 GB,这是很糟糕的。 而不是具有8 GB RAM实现的子代。
在这种情况下我会建议composition
。 但它会根据情景而改变。
通常承认通过inheritance扩展接口的实现不是最佳实践,并且该组合(例如,从头开始再次实现接口)更易于维护。
我认为这根本不准确。 当然,在很多情况下,组合比inheritance更受欢迎,但在很多情况下,inheritance比组合更受欢迎!
特别重要的是要意识到实现类的inheritance结构不需要看起来像API的inheritance结构。
有没有人真的相信,例如,当编写像Java一样的图形库时,每个实现类应该重新实现paintComponent()方法? 事实上,设计的一个主要原因是,当为新类编写绘制方法时,可以调用super.paint()并确保绘制层次结构中的所有元素,以及处理涉及与本机接口进一步交互的复杂性。在树上。
普遍接受的是,扩展不在您控制范围内且不是为了支持inheritance而设计的类是危险的,并且在实现发生变化时可能会引发烦人的错误。 (如果您保留更改实施的权利,请将您的课程标记为最终课程!)。 我怀疑Oracle会在ArrayList实现中引入重大变化! 如果你尊重它的文件,你应该没事….
这就是设计的优雅。 如果他们认为ArrayList存在问题,他们将编写一个新的实现类,类似于当天替换Vector时,并且不需要引入重大更改。
===============
在你当前的例子中,有效的问题是:为什么这个类存在?
如果您正在编写一个扩展list接口的类,那么它实现了哪些其他方法? 如果它没有实现新方法,那么使用ArrayList有什么问题?
当你知道答案时,你会知道该怎么做。 如果答案“我想要一个基本上是列表的对象,但有一些额外的便利方法来操作该列表”,那么我应该使用组合。
如果答案是“我想从根本上改变列表的function”,那么你应该使用inheritance,或者从头开始实现。 一个示例可能是通过重写ArrayList的add方法来实现不可修改的列表以引发exception。 如果您对这种方法感到不舒服,您可以考虑通过扩展AbstractList来实现,它实际上是为了inheritance而最大限度地减少重新实现的工作。