为什么有人想把课程标记为最终或密封?
答案 0 :(得分:14)
根据Wikipedia,“密封类主要用于防止派生。它们在编译时增加了另一级严格性,提高了内存使用率,并触发了某些优化,提高了运行时效率。”
此外,来自Patrick Smacchia's blog:
版本控制:当一个类最初被密封时,它可以在将来更改为未密封而不会破坏兼容性。 (...)
性能:(...)如果JIT编译器看到使用密封类型调用虚方法,JIT编译器可以通过非虚拟地调用该方法来生成更高效的代码。(...)
安全性和可预测性:一个类必须保护自己的状态,不允许自己被破坏。当一个类被解封时,如果内部操作字段的任何数据字段或方法是可访问的而不是私有的,派生类可以访问和操作基类的状态。(...)
这些都是很好的理由 - 在我刚才查看之前,我实际上并没有意识到性能优势的影响:)
版本和安全点在代码可信度方面似乎是一个巨大的好处,这在任何类型的大型项目中都是非常合理的。当然,对于单元测试来说,这并没有什么好处,但它会有所帮助。
答案 1 :(得分:10)
因为创建一个继承类型比大多数人想象的要困难得多。默认情况下,最好以这种方式标记所有类型,因为这样可以防止其他类型继承自从未打算扩展的类型。
是否应该扩展类型是由创建它的开发人员决定的,而不是稍后出现并希望扩展它的开发人员。
答案 2 :(得分:6)
Joshua Bloch在他的着作“Effective Java”中谈到了这一点。他说“继承或禁止它的文件”。 关键是类是作者和客户之间的契约。允许客户端从基类继承使得此合同更加严格。如果你要继承它,你很可能会覆盖一些方法,否则你可以用组合替换继承。允许覆盖哪些方法,以及实现它们必须执行的操作 - 应记录在案,否则您的代码可能会导致不可预测的结果。据我所知,他展示了这样的例子 - 这是一个带方法的集合类
public interface Collection<E> extends Iterable<E> {
...
boolean add(E e);
boolean addAll(Collection<? extends E> c);
...
}
有一些实现,即ArrayList。现在您希望从它继承并覆盖某些方法,因此它会在添加元素时打印以控制消息。现在,您是否需要覆盖添加和 addAll ,或仅添加?这取决于如何实现 addAll - 它是直接使用内部状态(如ArrayList所做)还是调用添加(如AbstractCollection所做的那样)。或者可能有 addInternal ,由添加和 addAll 调用。在你决定继承这门课之前,没有这样的问题。如果你只是使用它 - 它不会打扰你。因此,如果他希望你继承他的班级,那么该班的作者必须记录它。
如果他想在未来改变实施怎么办?如果只使用他的类,从不继承,那么没有什么能阻止他将实现更改为更高效。现在,如果您从该类继承,查看来源并发现 addAll 调用添加,则只覆盖添加。后来作者更改了实施,因此 addAll 不再调用添加 - 您的程序已损坏,调用 addAll 时不会打印消息。或者您查看了源代码,发现 addAll 未调用add,因此您覆盖添加和 addAll 。现在作者更改了实施,因此 addAll 调用添加 - 当您调用 addAll 时,您的程序会再次被破坏。每个元素都会打印两次消息。< / p>
所以 - 如果你想继承你的类,你需要记录如何。如果您认为将来可能需要更改可能会破坏某些子类的内容 - 您需要考虑如何避免它。通过让您的客户从您的类继承,您可以公开更多内部实现细节,当您让他们使用您的类时,您会公开内部工作流程,这通常会在未来版本中发生变化。
如果您公开某些细节而客户依赖它们 - 您将无法再更改它们。如果你没事,或者你记录了什么可以和什么不能被覆盖 - 那没关系。有时你只是不想要它。有时你只想说 - “只是使用这个类,永远不要继承它,因为我想要自由地改变内部实现细节。”
所以基本上评论“因为班级不想要任何孩子,我们应该尊重它的愿望”是正确的。
因此,有人想将一个类标记为最终/密封,当他认为可能的实现细节更改比继承更有价值时。还有其他方法可以实现类似于继承的结果。