最终和密封的目的

发布于 2024-08-05 09:54:13 字数 28 浏览 12 评论 0原文

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

Why would anyone want to mark a class as final or sealed?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

困倦 2024-08-12 09:54:13

根据维基百科,“密封类主要用于防止派生。它们在编译时添加另一个严格级别,提高内存使用率,并触发某些优化来提高运行时效率。”

另外,来自 Patrick Smacchia 的博客

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

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

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

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

版本控制和安全点似乎在代码信心方面具有巨大的优势,这对于任何类型的大型项目都是非常合理的。当然,它不能直接用于单元测试,但它会有所帮助。

According to Wikipedia, "Sealed classes are primarily used to prevent derivation. They add another level of strictness during compile-time, improve memory usage, and trigger certain optimizations that improve run-time efficiency."

Also, from Patrick Smacchia's blog:

  • Versioning: When a class is originally sealed, it can change to unsealed in the future without breaking compatibility. (…)

  • Performance: (…) if the JIT compiler sees a call to a virtual method using a sealed types, the JIT compiler can produce more efficient code by calling the method non-virtually.(…)

  • Security and Predictability: A class must protect its own state and not allow itself to ever become corrupted. When a class is unsealed, a derived class can access and manipulate the base class’s state if any data fields or methods that internally manipulate fields are accessible and not private.(…)

Those are all pretty good reasons - I actually wasn't aware of the performance benefit implications until I looked it up just now :)

The versioning and security points seem like a huge benefit in terms of code confidence, which is very well justified on any kind of large project. It's no drop-in for unit testing, of course, but it would help.

感情旳空白 2024-08-12 09:54:13

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

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

Because creating a type for inheritance is much harder work than most folks think. It is best to mark all types this way by default as this will prevent others from inheriting from a type that was never intended to be extended.

Whether or not a type should be extended is a decision of the developer who created it, not the developer who comes along later and wants to extend it.

澜川若宁 2024-08-12 09:54:13

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 时,每个元素都会打印两次消息。

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

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

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

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

Joshua Bloch in his book Effective Java talks about it. He says "document for inheritance or disallow it".
The point is that class is sort of a contract between author and client. Allowing client to inherit from base class makes this contract much more strict. If you are going to inherit from it, you most likely are going to override some methods, otherwise you can replace inheritance with composition. Which methods are allowed to be overridden, and what you have to do implementing them - should be documented, or your code can lead to unpredictable results. As far as I remember, he shows such example - here is a collection class with methods

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

There is some implementation, i.e. ArrayList. Now you want to inherit from it and override some methods, so it prints to console a message when element is added. Now, do you need to override both add and addAll, or only add? It depends on how addAll is implemented - does it work with internal state directly (as ArrayList does) or calls add (as AbstractCollection does). Or may be there is addInternal, which is called by both add and addAll. There were no such questions until you decided to inherit from this class. If you just use it - it does not bother you. So the author of the class has to document it, if he wants you to inherit from his class.

And what if he wants to change the implementation in the future? If his class is only used, never inherited from, nothing stops him from changing implementation to more efficient. Now, if you inherited from that class, looked at source and found that addAll calls add, you override only add. Later author changes implementation so addAll no longer calls add - your program is broken, message is not printed when addAll is called. Or you looked at source and found that addAll does not call add, so you override add and addAll. Now author changes implementation, so addAll calls add - your program is broken again, when addAll is called message is printed twice for each element.

So - if you want your class to be inherited from, you need to document how. If you think that you may need to change something in the future that may break some subclasses - you need to think how to avoid it. By letting your clients inherit from your class you expose much more of internal implementation details that you do when you just let them use your class - you expose internal workflow, that is often subject to changes in future versions.

If you expose some details and clients rely on them - you no longer can change them. If it is ok with you, or you documented what can and what can not be overriden - that's fine. Sometimes you just don't want it. Sometimes you just want to say - "just use this class, never inherit from it, because I want a freedom to change internal implementation details".

So basically comment "Because the class doesn't want to have any children and we should respect it's wishes" is correct.

So, someone wants to mark a class as final/sealed, when he thinks that possible implementation details changes are more valuable than inheritance. There are other ways to achieve results similar to inheritance.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文