继 承( 2)
辽宁经济职业技术学院信息系
硕士、副教授 陈英
动态绑定
调用一个对象方法的机制:
1) 编译器检查对象声明类型和方法名。
2) 编译器检查方法调用中的参数类型。
3) 如果方法类型为 private,static,final或
者是一个构造器,那么编译器也会准确地知
道该调用哪个方法,这称之为 静态绑定 。
4) 与此相反,靠隐式参数的实际类型来决定调
用哪个方法称之为 动态绑定,这必须发生在
运行时。
5) 当程序运行并且使用动态绑定来调用一个方
法时,那么虚拟机必须调用同个特定的类所
指向的对象的实际类型相匹配的方法版本。
方法的型构
一个方法的名字和参数类型列表称作该
方法的型构。
例如,f(int)和 f(String)是两个具有相
同名字但不同型构的方法。
如果在子类中定义一个同超类中某个方
法的型构完全相同的方法,那么你就重
载了超类中的那个方法。
动态绑定
在运行时,调用 e.getSalary()的过程是:
1) 虚拟机提取 e的实际类型的方法表。这可能
是 Employee,Manager或者是 Employee
超类的方法表。
2) 虚拟机在表中查找 getSalary()型构。现在
它知道该调用哪个方法了。
3) 虚拟机调用那个方法。
动态绑定的重要特性:它能使程序变得可扩展
而无需重编译已存在的代码。
若重载一个方法,子类方法必须至少具有超类
方法的可见性。特别是,如果超类方法为
public,那么子类方法必须也定义为 public。
防止继承 —— final类和方法
当 不希望别人从你的类上派生新类时,可以使用 final
修饰符定义在该类上,那么该类就是不可扩展的。
也可以把类中的一个方法声明为 final,它表示子类将不
能重载该方法。
把一个方法或一个类设为 final有两个原因,
1) 效率。动态绑定比静态绑定开销要大,所以进行动
态绑定的程序会更慢。
2) 安全。动态分配机制的灵活性意味着你不能完全控
制调用一个方法时所发生的一切。
例如 e.getName(),e是一个子类的对象,把方法
设为 final,则编译器可以把其替换成 e.name。
造型转换
把一个类型强制转换成另一个类型的过程称
作造型。
转换对象引用的语法同转换数字表达式的语
法一样:用圆括号把目标类名包起来,然后
把它放在你需要转换的对象引用前面。
例,Manager boss = (Manager) staff[0];
进行转换的惟一原因是:发挥实际对象类型
的全部功能。
在 java中,所有对象变量都有一个类型。类型
说明了对象所指向和能指向的对象的种类。
造型转换
如果把一个子类对象赋给超类变量,那么你无
需多做工作,编译器将为你完成;
如果把一个超类对象赋给子类对象,那么你就
需要多做一点工作:必须确信要进行造型转
换 —— 使用“子类”转换符号。
如果在进行继承链上的下溯造型时“作弊”的
话,情况会怎样呢?
Manager boss = (Manager)staff[1]; //error
当程序运行时,java运行系统发现转换不能进
行,就会抛出一个异常。如果不能捕获异常,
程序就会终止。
造型转换
?造型转换必须在继承层次内进行。
?在从超类到子类造型转换之前先使用
instanceof操作符进行检查。
事实上,通过执行造型操作来转换对象的
类型通常不是一个好想法。例如,不需要把
Employee对象造型成 Manager对象。两个类型
的 getSalary方法都会正确运行,这是因为多态
的工作机制 —— 动态绑定会自动选择正确的方
法。
操作符 instanceof
在接收父类的一个引用时,可以通过使用 Instanceof
运算符判定该对象实际上是否为你所要的子类,并
可以用类型转换该引用的办法来恢复对象的全部功
能。
public void method(Employee e) {
if (e instanceof Manager) {
// Get benefits and options along with salary
}else if (e instanceof Contractor) {
// Get hourly rates
}else {
// regular employee
}
}
如果不用
instanceof做测
试,就会有类
型转换失败的
危险。
操作符 instanceof
如果不用 instanceof做测试, 就会有类型转换失败的危
险 。 通常情况下, 类型转换一个对象引用的尝试是要经
过几种检查的:
1) 向上强制类型转换类层次总是允许的, 而且事实上
不需要强制类型转换运算符 。 可由简单的赋值实现
2) 对于向下类型转换, 编译器必须满足类型转换至少
是可能的 。 即 类型转换发生的类必须是当前引用类
型的子类 。
3) 如果编译器允许类型转换, 那么, 该引用类型就会
在运行时被检查 。 比如, 如果 instanceof检查从源
程序中被省略, 而被类型转换的对象实际上不是它
应被类型转换进去的类型, 那么, 就会发生一个运
行时错误 (exception)。 异常是运行时错误的一种
形式 。
覆盖方法
如果在新类中定义一个方法, 其名称, 返回类
型及参数表正好与父类中方法的名称, 返回类
型及参数相匹配, 那么, 新方法被称做 覆盖旧
方法 。
例如,Manager类有一个定义的 getSalary()方法,
因为它是从 Employee类中继承的 。 基本的方法
被子类的版本所代替或覆盖了 。
被执行的 e.getSalary()方法来自对象的真实类
型,Manager。
调用覆盖方法
子方法的名称以及子方法参数的顺序必须与父
类中的方法的名称以及参数的顺序相同以便该
方法覆盖父类版本 。 下述规则适用于覆盖方法,
1) 覆盖方法的返回类型必须与它所覆盖的方法
相同 。
2) 覆盖方法不能比它所覆盖的方法访问性差
3) 覆盖方法不能比它所覆盖的方法抛出更多的
异常 。
这些规则源自多态性 的属性和 Java编程语言
必须保证“类型安全”的需要。
抽象类 —— abstract
当你从下往上看继承层次图的时候,就会发现
类逐渐变得更通用,可能也更抽象。
祖先类由于非常通用化,所以可以把它看成其
它类的基础,而不是一个需要使用它的几个实
例类。
在 java中,用 abstract关键字修饰不能在这个类
内实现的方法,这样的方法称为抽象方法。
如果一个类含有一个或多个抽象方法,则该类
也必须声明为 abstract,把用 abstract关键字修
饰的类称为抽象类。
抽象类中不一定包含抽象方法,包含了抽象方
法的类一定要被声明为抽象类。
抽象类 —— abstract
抽象方法形同占位符,它需要在子类中实现。
当扩展一个抽象类时,有两个选择:
1) 可以不定义 部分或全部的抽象方法,这就必
须也在子类前加上 abstract标记。
2) 定义的所有的抽象方法,这样子类就不再是
抽象类。
抽象类不能实例化。即,如果一个类被定义为
抽象类,那么就不能构建这个类的任何对象。
但可以构建具体子类的对象。
尽管可以创建抽象类的对象变量,但是这个变
量必须指向非抽象子类的对象