1. 继承层次。
继承并不仅限于类的一个层次。而是可以由派生的类再派生。例如,可以由Manager类扩展Exective类。由一个公共超类派生出来的所有类的集合被称为继承层次。在继承层次中,从某个特定的类到其祖先的路径被称为该类的继承链。
通常,一个祖先类可以拥有多个祖孙继承链。
2. 多态。
一个对象变量可以引用多种实际类型的现象被称为多态。在Java程序设计语言中,对象变量是多态的。一个Employee类型变量既可以引用一个Employee类型对象,也可以引用一个Employee类的任何一个子类的对象(例如,Manager,Executive等)。看下面的例子:
Manager boss = new Manager(...);
Employee[] staff = new Employee[3];
staff[0] = boss;
在这个例子中,变量staff[0]与boss引用同一个对象。但编译器将staff[0]看成Employee对象。这意味着可以这样调用:
boss.setBonus(5000) //ok
但不能这样调用:
staff[0].setBonus(5000); //error
这是因为staff[0]声明的类型是Employee,而setBonus不是Employee类的方法。然而,不能将一个超类的引用赋给子类变量。原因很清楚,不是所有的雇员都是经历。如果赋值成功,后面调用的时候就有可能发生运行时错误。
3. 多态绑定。
在运行时能够自动地选择调用的适当方法的现象称为动态绑定。弄清楚调用对象方法的执行过程十分重要。下面是调用过程的详细描述:
1). 编译器查看对象的声明类型和方法名。
假设调用x.f(param),且x被声明为C类的对象。需要注意的是:有可能存在多个名字为f,但参数类型不一样的方法。例如,可能存在方法f(int)和方法f(String)。编译器将会一一列举所有C类中名为f的所有方法和其超类中访问属性为public且名为f的方法。
至此,编译器已经获得所有可能被调用的候选方法。
2). 接下来,编译器将查看调用方法时提供的参数类型。如果在所有名为f的方法中存在一个其参数类型与提供的参数完全匹配,那么就调用这个方法。这个过程被称为重载解析。例如,对于调用x.f("Hello")来说,编译器将会挑选f(String),而不是f(int)。由于允许类型转换(int可以转换成double,Manager可以转换成Employee等等),所以这个过程可能很复杂。如果编译器没有找到与参数类型匹配的方法,或者发现经过类型转换后有多个方法与之匹配,就会报告一个错误。
至此,编译器已经获得需要调用的方法的名字和参数类型。
3). 如果方法是private、static、final或者构造器,那么编译器将可以准确地知道应该调用哪个方法。我们将这种调用方式称为静态绑定。与之对应,调用哪个方法将依赖于隐式参数的实际类型,并且在运行时动态绑定。
4). 当程序运行,并且采用动态绑定调用方法时,虚拟机一定调用与x所引用对象的实际类型最合适的那个类的方法。
4. 阻止继承:final类和final方法。
有时候,可能希望阻止人们利用某个类定义子类。不允许扩展的类被称为final类。如果在定义类的时候使用了final修饰符就表明这个类是final类。格式: final class Manager extends Employee { ... } 这样的话Manager类就不能被继承。
类中的方法也可以被声明为final。如果这样,子类就不能覆盖这个方法。final类中的所有方法自动称为final方法。例如:
class Employee {
public final String getName() {
return name;
}
}
前面已经说过,域也可以被声明为final。对于final域来说,构造对象之后就不允许改变他们的值了。不过,如果将一个类声明为final,只有其中的方法自动地称为final,而不包括域。