有关有效使用final关键字的准则
2008-01-05 20:20:56 来源:WEB开发网final 要害字经常被误用 - 声明类和方法时使用过度,而声明实例字段时却使用不足。本月,java 实践者 Brian Goetz 探究了一些有关有效使用 final 的准则。
如同它的“表亲”- C 中的 const 要害字一样,根据上下文,final 表示不同的东西。final 要害字可应用于类、方法或字段。应用于类时,意味着该类不能再生成子类。应用于方法时,意味着该方法不能被子类覆盖。应用于字段时,意味着该字段的值在每个构造器内必须只能赋值一次而且此后该值永远不变。
大多数 Java 文本都适当地描述了使用 final 要害字的用法和后果,但是很少以准则的方式提供有关何时使用 final 及使用频率的内容。根据我的经验,final 非常过度地用于类和方法(通常是因为开发人员错误地相信这会提高性能),而在其用武之地 - 声明类实例变量 - 却使用不足。
为什么这个类是 final?
对于开发人员来说,将类声明为 final,却不给出为何作出这一决定的说明,这样的做法很普遍,在开放源码项目中尤其如此。一段时间之后,非凡是假如原来的开发人员不再参与代码的维护,其它开发人员将总是发问“为何类 X 被声明成 final?”。通常没人知道,当有人确实知道或喜欢猜测时,答案几乎总是“因为这能使它运行得更快”。普遍的理解是:将类或方法声明成 final 会使编译器更轻易地内联方法调用,但是这种理解是不正确的(或者至少说是大大地言过其实了)。
final 类和方法在编程时可能是非常大的麻烦 - 它们限制您选择重用已有的代码和扩展已有类的功能。有时有很好的理由将类声明成 final(如强制不变性),此时使用 final 的益处将大于其不便之处。性能提高几乎总是成为破坏良好的面向对象设计原则的坏理由,而当性能提高很小或者根本没有提高时,则它真正是个很差的权衡方法。
过早优化
出于性能的考虑,在项目的早期阶段将方法或类声明成 final 是个坏主意,这有多个原因。首先,早期阶段设计不是考虑循环计算性能优化的时候,尤其当此类决定可能约束您使用 final 进行设计。其次,通过将方法或类声明成 final 而获得的性能优势通常为零。而且,将复杂的有状态的类声明成 final 不利于面向对象的设计,并导致体积庞大且面面俱到的类,因为它们不能轻松地重构成更小更紧凑的类。
和许多有关 Java 性能的神话一样,将类或方法声明成 final 会带来更佳的性能,这一错误观念被广泛接受但极少进行检验。其论点是:将方法或类声明成 final 意味着编译器可以更加积极地内联方法调用,因为它知道在运行时这正是要调用的方法的版本。但这显然是不正确的。仅仅因为类 X 编译成 final 类 Y,并不意味着同样版本的类 Y 将在运行时被装入。因此编译器不能安全地内联这样的跨类方法调用,不管是不是 final。只有当方法是 PRivate 时,编译器才能自由地内联它,在这种情况下,final 要害字是多余的。
另一方面,运行时环境和 JIT 编译器拥有更多有关真正装入什么类的信息,可以比编译者作出好得多的优化决定。假如运行时环境知道没有装入继续 Y 的类,那么它可以安全地内联对 Y 方法的调用,不管 Y 是不是 final(只要它能在随后装入 Y 子类时使这种 JIT 编译的代码无效)。因此事实是,尽管 final 对于不执行任何全局相关性分析的“哑”运行时优化器可能是有用的,但它的使用实际上不支持太多的编译时优化,而且智能的 JIT 执行运行时优化时不需要它。
似曾相识 - 重新回忆 register 要害字
final 用于优化决定时和 C 中不赞成使用的 register 要害字非常相似。让程序员帮助优化器这一愿望促成了 register 要害字,但事实上,发现这并不是很有用。正如我们在其它方面愿意相信的那样,在作出代码优化决定方面编译器通常比人做得出色,在现在的 RISC 处理器上更是如此。事实上,大多数 C 编译器完全忽略了 register 要害字。早先的 C 编译器忽略它是因为这些编译器根本就不起优化作用;现今的编译器忽略它是因为编译器不用它就能作更好的优化决定。任何一种情况下,register 要害字都没有添加什么性能优势,和应用于 Java 类或方法的 final 要害字很相似。假如您想优化您的代码,请坚持使用那些可以大幅度提高性能的优化,比如使用有效的算法且不执行冗余的计算 - 将循环计算优化留给编译器和 JVM 去做。
使用 final 保持不变性
虽然性能并不是将类或方法声明为 final 的好理由,然而有时仍有充足的理由编写 final 类。最常见的是 final 保证那些旨在不发生变化的类保持不变。不变类对于简化面向对象程序的设计非常有用 - 不变的对象只需要较少的防御性编码,并且不要求严格的同步。您不会在您的代码中构建这一设想:类是不变的,然后让某些人用使其可变的方式来继续它。将不变的类声明成 final 保证了这类错误不会偷偷溜进您的程序中。
final 用于类或方法的另一个原因是为了防止方法间的链接被破坏。例如,假定类 X 的某个方法的实现假设了方法 M 将以某种方式工作。将 X 或 M 声明成 final 将防止派生类以这种方式重新定义 M,从而导致 X 的工作不正常。尽管不用这些内部相关性来实现 X 可能会更好,但这不总是可行的,而且使用 final 可以防止今后这类不兼容的更改。
假如您必须使用 final 类或方法,请记录下为什么这么做
无论何种情况,当您确实选择了将方法或类声明成 final 时,请记录下为什么这样做的原因。否则,今后的维护人员将可能迷惑这样做是否有好的原因(因为经常没有);而且会被您的决定所约束,但同时还不知道您这样做的动机是为了得到什么好处。在许多情况下,将类或方法声明成 final 的决定一直推迟到开发过程后期是有意义的,那时您已经拥有关于类是如何交互及可能如何被继续的更好信息了。您可能发现您根本不需要将类声明为 final,或者您可以重构类以便将 final 应用于更小更简单的类。
更多精彩
赞助商链接