5.1.4 阻止继承:final类和方法

    有时候,可能希望阻止人们利用某个类定义子类。不允许扩展的类被称为final类。如果在定义类的时候使用了final修饰符就表明这个类是final类。例如,假设希望阻止人们定义Executive类的子类,就可以在定义这个类的时候,使用final修饰符声明。声明格式如下所示:

  
  
  
  
  1. final class Executive extends Manager 
  2.     .... 

    类中的方法也可以被声明为final。如果这样做,子类就不能覆盖这个方法(final类中的所有方法自动地称为final方法)。例如

  
  
  
  
  1. class Employee 
  2.     .... 
  3.     public final String getName() 
  4.     { 
  5.         return name; 
  6.     } 
  7.     .... 

    注释:前面曾经说过,域也可以被声明为final。对于final域来说,构造对象之后就不允许改变它们的值了。不过,如果将一个类声明为final,只有其中的方法自动地成为final,而不包括域。

    将方法或类声明为final主要鉴于一下原因:确保它们不会在子类中改变语义。例如,Calendar类中的getTime和setTime方法都声明为final。这表明Calendar类的设计者负责实现Date类与日历状态之间的转换,而不允许子类处理这些问题。同样地,String类也是final类,这意味着不允许任何人定义String类的子类。换言之,如果有一个String的引用,它引用的一定是一个String对象,而不可能是其他类的对象。

    有些程序员认为:除非有足够的理由使用多态性,应该将所有的方法都声明为final。事实上,在C++和C#中,如果没有特别的说明,所有的方法都不具有多态性。这两种做法可能都有些偏激。我们提倡在设计类层次时,仔细地思考应该将哪些方法和类声明为final。

    在早期的Java中,有些程序员为了避免动态绑定带来的系统开销而使用final关键字。如果一个方法没有被覆盖并且很短,编译器就能够对它进行优化处理,这个过程称为内联(inlining)。例如,内联调用e.getName()将被替换为访问e.name域。这是一项很有意义的改进,这是由于CPU在处理调用方法的指令时,使用的分支转移会扰乱预取指令的策略,所以,这被视为不受欢迎的。然而,如果getName在另外一个类中被覆盖,那么编译器就无法知道覆盖的代码将会做什么操作,因此也就不能对它进行内联处理了。

    幸运的是,虚拟机中的即时编译器比传统编译器的处理能力强得多。这种编译器可以准备地知道类之间的继承关系,并能够检测出类中是否真正地存在覆盖给定的方法。如果方法很简短、被频繁调用且没有真正地被覆盖,那么及时编译器就会将这个方法进行内联处理。如果虚拟机加载了另外一个子类,而在这个子类中包含了对内联方法的覆盖,那么将会发生什么情况呢?优化器将取消对覆盖方法的内联。这个过程很慢,但却很少发生。

    C++注释:C++中的方法在默认情况下不是动态绑定的,而是利用inline标记将方法调用替换成方法的源代码。不过,在C++中,没有提供任何机制阻止一个子类覆盖超类的方法。在C++中,也能够定义一个不允许被派生的类,但这需要使用一定的技巧,并且这样做也没有什么意义(这个问题将作为练习留给读者。提示:使用虚基类)。

你可能感兴趣的:(Java核心技术I)