9

为什么会有人想将一个类标记为最终类或密封类?

4

3 回答 3

14

根据Wikipedia的说法,“密封类主要用于防止派生。它们在编译时增加了另一层严格性,提高了内存使用率,并触发了某些提高运行时效率的优化。”

此外,来自Patrick Smacchia 的博客

  • 版本控制:当一个类最初是密封的时,它可以在将来更改为未密封的,而不会破坏兼容性。(……)

  • 性能:(...)如果 JIT 编译器看到使用密封类型对虚拟方法的调用,则 JIT 编译器可以通过非虚拟方式调用该方法来生成更高效的代码。(...)

  • 安全性和可预测性:一个类必须保护自己的状态,并且不允许自己被破坏。当一个类被解封时,如果任何内部操作字段的数据字段或方法是可访问的而不是私有的,则派生类可以访问和操作基类的状态。(…)

这些都是很好的理由 - 实际上,直到我刚刚查看它时,我才意识到性能优势的影响:)

就代码信心而言,版本控制和安全点似乎是一个巨大的好处,这在任何类型的大型项目中都是非常合理的。当然,它不是单元测试的插件,但它会有所帮助。

于 2009-09-20T03:52:13.837 回答
10

因为创建继承类型比大多数人想象的要困难得多。默认情况下最好以这种方式标记所有类型,因为这将防止其他人继承从未打算扩展的类型。

是否应该扩展一个类型是由创建它的开发人员决定的,而不是后来出现并想要扩展它的开发人员的决定。

于 2009-09-20T03:50:13.467 回答
6

Joshua Bloch 在他的《Effective Java》一书中谈到了这一点。他说“继承文件或不允许它”。关键是类是作者和客户之间的一种合同。允许客户端从基类继承使得这个契约更加严格。如果你要继承它,你很可能会覆盖一些方法,否则你可以用组合替换继承。哪些方法可以被覆盖,以及你必须做些什么来实现它们——应该记录在案,否则你的代码可能会导致不可预测的结果。据我记得,他展示了这样的例子 - 这是一个带有方法的集合类

public interface Collection<E> extends Iterable<E> {    
  ...
  boolean add(E e);
  boolean addAll(Collection<? extends E> c);
  ...
}

有一些实现,即ArrayList。现在您想从它继承并覆盖一些方法,因此在添加元素时它会打印以控制台消息。现在,您需要同时覆盖addaddAll,还是只覆盖add?这取决于addAll是如何实现的——它是直接与内部状态一起工作(如 ArrayList 所做的那样)还是调用add(如 AbstractCollection 所做的那样)。或者可能有addInternal,它由addaddAll 调用. 直到你决定从这个类继承之前,没有这样的问题。如果您只是使用它 - 它不会打扰您。所以类的作者必须记录它,如果他想让你从他的类继承。

如果他想在未来改变实施怎么办?如果他的类只被使用,从不继承,没有什么能阻止他改变实现以提高效率。现在,如果您从该类继承,查看源代码并发现addAll调用add,您只覆盖add。后来作者更改了实现,因此addAll不再调用add - 您的程序已损坏,调用addAll时不会打印消息。或者您查看源代码并发现addAll不调用 add,因此您覆盖addaddAll。现在作者更改了实现,所以addAll调用add - 你的程序再次被破坏,当addAll被调用时,每个元素都会打印两次消息。

所以 - 如果你希望你的类被继承,你需要记录如何。如果您认为将来可能需要更改某些可能会破坏某些子类的内容-您需要考虑如何避免它。通过让您的客户从您的类继承,您可以公开更多当您让他们使用您的类时所做的内部实现细节 - 您公开了内部工作流程,这通常会在未来版本中发生变化。

如果您公开了一些细节并且客户依赖它们 - 您将无法再更改它们。如果你没问题,或者你记录了什么可以和什么不能被覆盖 - 那很好。有时你只是不想要它。有时你只想说——“只使用这个类,永远不要从它继承,因为我想要自由更改内部实现细节”。

所以基本上评论“因为班级不想生孩子,我们应该尊重他们的意愿”是正确的。

因此,当某人认为可能的实现细节更改比继承更有价值时,他想将一个类标记为最终/密封。还有其他方法可以实现类似于继承的结果。

于 2009-09-21T19:44:56.983 回答