7.8.2 final方法
使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义。这是出于设计的考虑:想要确保在继承中使方法行为保持不变,并且不会被覆盖。过去建议使用final方法的第二个原因是效率。在Java的早期实现中,如果将一个方法指明为final,就是同意编译器将针对该方法的所有调用都转为内嵌调用。当编译器发现一个final方法调用命令时,它会根据自己的谨慎判断,跳过插入程序代码这种正常方式而执行方法调用机制(将参数压入栈,跳至方法代码处并执行,然后跳回并清理栈中的参数,处理返回值),并且以方法体中的实际代码的副本来替代方法调用。这将消除方法调用开销。当然,如果一个方法很大,你的代码就会膨胀,因而可能看不到内嵌带来的任何性能提高,因为,所带来的性能提高会因为花费于方法内的时间量而被缩减。在现在的Jvm版本中,不再需要使用final方法来进行性能优化了。
7.8.3 final类
当将某个类的整体定义为final时,就表明了你不打算继承该类,而且也不允许别人这样做。换句话说,出于某种考虑,你对该类的设计永不需要做任何变动,或者出于安全的考虑,你不希望它有子类。
//: reusing/Jurassic.java // Making an entire class final. classSmallBrain {} finalclassDinosaur { inti=7; intj=1; SmallBrainx=newSmallBrain(); voidf() {} } //! class Further extends Dinosaur {} // error: Cannot extend final class ‘Dinosaur’ publicclassJurassic { publicstaticvoidmain(String[] args) { Dinosaurn=newDinosaur(); n.f(); n.i=40; n.j++; } }
///:~
7.8.4 有关final的忠告
在设计类时,将方法指明是final的,应该说是明智的。你可能会觉得,没人会想要覆盖你的方法。有时这是对的。但请留意你所做的假设。要预见类是如何被复用的一般是很困难的,特别是对于一个通用类而言更是如此。如果将一个方法指定为final,可能会妨碍其他程序员在项目中通过继承复用你的类,而这只是因为你没有想到它会以那种方式被运用。
7.9 初始化及类的加载
在许多传统语言中,程序是作为启动过程的一部分立刻被加载的。然后是初始化,紧接着程序开始运行。这些语言的初始化过程必须小心控制,以确保定义为static的东西,其初始化顺序不会造成麻烦。例如C++中,如果某个static期望另一个static在被初始化之前就能有效地使用它,那么就会出现问题。
Java就不会出现这个问题,因为它采用了一种不同的加载方式。加载是众多变得更加容易的动作之一,因为Java中的所有事物都是对象。请记住,每个类的编译代码都存在于它自己的独立的文件中。该文件只在需要使用程序代码时才会被加载。一般来说,可以说“类的代码在初次使用时才加载。”这通常是指加载发生于创建类的第一个对象之时,但是当访问static域或static方法时,也会发生加载。初始使用之处也是static初始化发生之处。所有static对象和static代码段都会在加载时依程序中的顺序而依次初始化。当然,定义为static的东西只会被初始化一次。
7.9.1 继承与初始化
了解包括继承在内的初始化全过程,以对所发生的一切有个全局性的把握,是很有益的。如:
importstaticnet.mindview.util.Print.*; classInsect { privateinti=9; protectedintj; Insect() { print("i = "+i+", j = "+j); j=39; } privatestaticintx1=printInit("static Insect.x1 initialized"); staticintprintInit(Strings) { print(s); return47; } } publicclassBeetleextendsInsect { privateintk=printInit("Beetle.k initialized"); publicBeetle() { print("k = "+k); print("j = "+j); } privatestaticintx2=printInit("static Beetle.x2 initialized"); publicstaticvoidmain(String[] args) { print("Beetle constructor"); Beetleb=newBeetle(); } } /* Output: static Insect.x1 initialized static Beetle.x2 initialized Beetle constructor i = 9, j = 0 Beetle.k initialized k = 47 j = 39
*///:~
7.10 总结
继承和组合都能从现有类型生成新类型。组合一般是将现有类型作为新类型底层实现的一部分来加以复用,而继承复用的是接口。
在使用继承时,由于导出类具有基类接口,因此它可以向上转型至基类,这对于多态来讲至关重要。
尽管面向对象编程对继承极力强调,但在开始一个设计时,一般应优先选择使用组合或者代理,只在确实必要时才使用继承。因为组合更具灵活性。此外,通过对成员类型使用继承技术的添加技巧,可以在运行时改变那些成员对象的类型和行为。因此,可以在运行时改变组合而成的对象的行为。
在设计一个系统时,目标应该是找到或者创建某些类,其中每个类都有具体的用途,而且既不会太大(包含太多的功能而难以复用),也不会太小(不添加其他功能就无法使用)。如果你的设计变得过去复杂,通过将现有类拆分为更小的部分而添加更多的对象,通常会有所帮助。
当你开始设计一个系统时,应该认识到程序开发是一种增量过程,犹如人类的学习一样,这一点很重要。程序开发依赖于实验,你可以尽己所能去分析,但当你开始执行一个项目时,你仍然无法知道所有的答案。如果将项目视作是一种有机的,进化的生命体去培养,而不是打算像盖摩天大楼一样快速见效,就会获得更多的成果和更迅速的回馈。继承与组合正是在面向对象程序设计中使得你可以执行这种实验的最基本的两个工具。