有什么好的理由来禁止Java中的继承,例如使用一个单独的、私有的无参数构造函数来使用最终类或类?最终确定方法的好理由是什么?
- 迂回:默认构造函数是Java编译器为您生成的构造函数,如果您没有在代码中显式地编写一个构造函数。您的意思是无参数构造函数。
- 这不是"隐式默认构造函数"吗?
- "您不必为类提供任何构造函数,但这样做时必须小心。编译器为没有构造函数的任何类自动提供一个无参数的默认构造函数。"Java.Sun.com/DOCS/Booo/Budio/Java/javao/构造函数.HT和ZWNJ;& 8203;ML - John Topley
你最好的参考是Joshua Bloch的优秀著作《有效Java》的第19条,名为"设计和文档继承或其他禁止"。(这是第二版的第17项,第一版的第15项。)你真的应该读一下,但我要总结一下。
如果祖先不是被设计来继承的,那么继承类与父类之间的交互可能会令人惊讶和不可预测。
因此,类应该分为两类:
设计用于扩展的类,并且有足够的文档来描述应该如何完成
标记为final的类
如果您编写的是纯内部代码,这可能有点过分了。但是,在类文件中添加五个字符所需的额外工作非常少。如果您只是为内部消耗而写,那么未来的编码人员总是可以删除"final",您可以将其视为一个警告:"这个类的设计思想中没有继承性"。
- 这是一个很好的答案,但在大多数情况下都有点过分了。
- 根据我的经验,如果除了我以外的任何人都要使用代码,这并不是多余的。我从来没有理解为什么Java在默认情况下有方法可重写。
- 要理解一些有效的Java,你需要了解Josh的设计。他说[类似]你应该总是把类接口设计成一个公共的API。我认为大多数人的意见是,这种方法通常过于沉重和僵化。(雅格尼等)
- 回答得好。(我忍不住指出这是六个字符,因为还有一个空格…;-)
- 我有没有办法把这个标记为"接受的答案";)
- 请注意,使类成为最终类可能会使测试更加困难(难以存根或模拟)
- 如果最后一个类正在写入接口,那么模拟不应该是一个问题。
- 在没有源代码的接口上删除final是非常困难的,并且能够子类覆盖方法可能是必需的。
- 为了避免人们查找,第二版中的第17项是"设计和文档用于继承或禁止继承"。
- @你是说上课吗?带有最终修饰符的接口(甚至是抽象类)听起来不像一个好的设计。
- 这不是库/框架编写器和应用程序编写器之间的区别。Tom Hawtin所描述的"过于沉重和不灵活"在您继承他人编写的代码时变得至关重要,并且不能贬低设计中的任何错误,因为原始开发人员认为一切都应该公开。通常,您应该总是设计包级的最终类,这些类在公共接口上实现方法。如果以后需要更改接口,则使用适配器。如果不这样做,就意味着一旦部署了软件,就永远无法修复设计错误。即)爪哇
- 我在默认情况下将所有内容都标记为最终的过程中遇到的困难是,它假定一个没有尽力使类适合扩展的开发人员将花费一定的时间来确保它实现了一个设计良好的接口以方便测试。要么开发人员懒惰,在这种情况下,没有接口,它被标记为final,要么开发人员是面向细节的,在这种情况下,类支持扩展,不需要是final。
- 我已经开始阅读这个帖子中提到的"有效Java"。了解最佳实践似乎很好
- (第三版第19项)
您可能希望使一个方法成为最终方法,这样重写类就不能更改其他方法所依赖的行为。在构造函数中调用的方法通常被声明为final,这样在创建对象时就不会有任何不愉快的意外。
- 不太明白这个答案。如果你不介意,你能解释一下你所说的"重写类不能改变其他方法所依赖的行为"是什么意思吗?首先是因为我不理解这个句子,其次是因为NetBeans建议我从构造函数调用的函数之一应该是final。
- @nav如果您使一个方法成为最终的方法,那么一个重写类将不能拥有它自己的方法版本(或者它将是一个编译错误)。这样,您就知道在该方法中实现的行为在稍后其他人扩展类时不会改变。
使类最终化的一个原因是,如果您想强制组合而不是继承。这通常是为了避免类之间的紧密耦合。
有3个用例可以用于最终方法。
以避免派生类重写特定的基类功能。
这是出于安全目的,其中基类提供框架的一些重要核心功能,而派生类不应该更改框架。
最终方法比实例方法更快,因为最终方法和私有方法不使用虚拟表概念。所以在有可能的地方,试着使用最后的方法。
课程最终确定的目的:
所以没有人能够扩展这些类并改变它们的行为。
包装类整数是最后一个类。如果该类不是最终类,那么任何人都可以将integer扩展到自己的类中,并更改integer类的基本行为。为了避免这种情况,Java将所有包装类作为最终类。
- 对你的例子不太信服。如果是这样,那么为什么不让方法和变量成为最终结果,而不是让整个类成为最终结果呢?你能用细节编辑一下你的答案吗?
- 即使将所有变量和方法都设为最终变量,其他变量和方法也可以继承类,添加额外的功能,并更改类的基本行为。
- >如果该类不是最终类,那么任何人都可以将integer扩展到自己的类中,并更改integer类的基本行为。假设这是允许的,这个新类被称为DerivedInteger,它仍然不会改变原来的整数类,而使用DerivedInteger的任何人都有自己的风险,所以我仍然不明白为什么这是一个问题。
您可能希望创建不可变的对象(http://en.wikipedia.org/wiki/immutable_object),您可能希望创建一个singleton(http://en.wikipedia.org/wiki/singleton_pattern),或者出于效率、安全或安全的原因,您可能希望阻止某人重写该方法。
继承就像一把链锯-非常强大,但在坏人手中却很可怕。或者您设计了一个要从中继承的类(这会限制灵活性并花费更长的时间),或者您应该禁止它。
查看有效的Java第二版项目16和17,或我的博客帖子"遗产税"。
- 到博客文章的链接已断开。另外,你认为你能详细阐述一下吗?
- @迈克尔特:修复了链接,谢谢-按照它来做进一步的阐述:)(恐怕我现在没有时间添加这个了。)
- @Jonskeet,博客帖子的链接断了。
- @凯达纳特:它对我有用……它被打破了一段时间,但现在它指向正确的页面,在codeblog.jonsket.uk上…
- @琼斯基特,是的,现在工作得很好。谢谢
六羟甲基三聚氰胺六甲醚。。。我可以想到两件事:
您可能有一个处理某些安全问题的类。通过对其进行子类化并向系统提供子类化版本,攻击者可以绕过安全限制。例如,您的应用程序可能支持插件,如果一个插件可以将您的安全相关类划分为子类,那么它可以使用这个技巧以某种方式将它的子类版本走私到适当的位置。但是,这是Sun必须处理的关于小程序之类的问题,可能不是这样一个现实的情况。
一个更现实的方法是避免对象变为可变的。例如,由于字符串是不可变的,因此您的代码可以安全地保留对它的引用。
1
| String blah = someOtherString ; |
而不是先复制字符串。但是,如果可以对字符串进行子类化,则可以向其中添加允许修改字符串值的方法,现在没有任何代码可以再依赖于,如果字符串只是如上所述复制字符串,那么它将保持不变,而必须复制字符串。
如果您将类和方法标记为最终的,您可能会注意到一个小的性能提升,因为运行时不必查找为给定对象调用的正确类方法。非final方法被标记为virtual,以便在需要时对其进行适当扩展,final方法可以在类中直接链接或内联编译。
- 我相信这是一个神话,因为当代的虚拟机应该能够根据需要进行优化和去优化。我记得看到有人把这当作一个神话。
- 代码生成的不同不是一个神话,但性能的提高可能是。正如我所说的,这是一个小的收益,一个站点提到了3.5%的性能收益,有些更高,在大多数情况下,这不值得把所有的纳粹代码。
- 这不是神话。实际上,编译器只能内联v final方法。我不确定是否有任何JIT"内联"的东西艺术运行时,但我怀疑它可以,因为您以后可能会加载一个派生类。
- 微基准==盐粒。这就是说,在10b周期预热之后,在我的笔记本电脑上,超过10b的平均通话量在Eclipse下基本上没有什么区别。两种方法返回字符串,final为6.9643us,non final为6.9641us。那个三角洲是背景噪音,imho。
另外,如果您正在编写一个商业性的封闭源代码类,您可能不希望人们能够直接更改功能,特别是如果您需要对其提供支持,并且人们已经重写了您的方法,并且抱怨调用它会产生意想不到的结果。
阻止人们做那些会使自己和他人困惑的事情。想象一个物理库,其中有一些定义的常量或计算。如果不使用final关键字,有人可能会过来重新定义基本计算或永远不会改变的常量。
- 关于这个论点,我有些不明白的地方——如果有人改变了那些不应该改变的计算/常数——难道不是因为100%的错误而导致的任何失败吗?换言之,这种变化对什么都有好处?
- 是的,这在技术上是"他们的"错误,但是想象一下,如果有人使用库,而没有意识到这些变化,可能会导致混乱。我一直认为这就是许多Java基类被标记为最终的原因,以阻止人们改变基本功能。
- @MattB-你似乎在假设做出更改的开发人员是故意这样做的,或者他们会在意这是他们的错。如果除我之外的其他人将使用我的代码,我将把它标记为最终版本,除非它是要更改的。
- @安森,@eric-很好。谢谢你把它清理干净
- 这实际上是与被问到的"final"不同的用法。
- 这个答案似乎将单个字段的继承性和可变性与编写子类的能力混淆了。您可以将各个字段和方法标记为最终字段和方法(防止它们重新定义),而不禁止继承。
您希望使一个方法成为最终方法,这样重写类就不会改变它的行为。当您希望能够更改行为时,请将该方法设为公共方法。当您重写一个公共方法时,它可以被更改。