多态
Java引用变量有两种类型:编译时类型、运行时类型。
编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。对于如下代码,f 的编译时类型就是 Father 类型,运行时类型是 Son 类型。
Father f = new Son();
如果编译时类型和运行时类型不一致,就有可能出现所谓的多态性。
-
多态的优点
- 提高了代码的维护性(继承保证)
- 提高了代码的扩展性(多态保证)
多态前提和演示
多态的前提条件
-
要有继承关系;
-
要有方法重写;
-
要有父类引用指向子类对象。
多态的代码演示
// 示例代码01
class Father {
public int age = 40;
public void base(){
System.out.println("父类自己的方法");
}
public void test(){
System.out.println("父类要被覆盖的方法");
}
}
public class Son extends Father {
public String age = "今年6岁啦";
public void test(){
System.out.println("子类覆盖了父类的方法");
}
public void self(){
System.out.println("子类自己的方法");
}
public static void main(String[] args){
// 编译时类型和运行时类型不同,将发生多态特征
Father f = new Son();
// 将访问父类的实例变量
System.out.println(f.age);
// 将调用从父类继承到的base()方法
f.base();
// 将调用子类重写的test()方法
f.test();
// 因为变量f编译时的类型是Father,但Father类没有提供self()方法,所以下面的代码编译时会出错
// f.self();
}
}
分析: 当把一个子类对象直接赋值给父类引用变量时,正如上面的 Father f = new Son();,这个 f 引用变量的编译时类型是 Father,而运行时类型是 Son,当运行时调用该引用变量的方法时,其方法行为总是表现出子类方法的行为特征,而不是父类方法的行为特征。这就出现:相同类型的变量、调用同一方法时呈现出多种不同的行为特征,这就是多态。
多态特点总结
通过引用变量来访问其包含的实例变量时,系统总是试图访问它编译时类型所定义的成员变量,而不是它运行时类型所定义的成员变量。
引用变量在编译阶段只能调用其编译时类型所具有的方法,但运行时则执行它运行时类型所具有的方法。
可以从三个方面简记多态的特点。对于类似下面的代码:
Father f = new Son();
-
访问成员变量:
编译看左边,运行看左边。 -
访问非静态成员方法:
编译看左边,运行看右边。(动态绑定) -
访问静态成员方法:
编译看左边,运行看右边。(静态和类相关,算不上是重写。所以访问还是看左边)
引用变量的强制类型转换
在编写Java程序时,引用变量只能调用它编译时类型的方法,而不能调用它运行时类型的方法,即使它实际所引用的对象的确包含该方法。
如果需要让这个引用变量调用它运行时类型的方法,则必须把它强制类型转换成运行时类型。
强制类型转换简单示范
强制类型转换的需求体现在 “示例代码01” 的如下片段:
// 因为变量f编译时的类型是Father,但Father类没有提供self()方法,所以下面的代码编译时会出错
// f.self();
想要调用子类的 self()方法,需要将 f 进行强制类型转换,如下代码即可实现调用子类的 self() 方法:
// 强制类型转换
Son s = (Son) f;
s.self();
借助instanceof运算符进行强制类型转换
instanceof 运算符用于判断前面的对象是否是后面的类、子类或是实现类的实例。如果是则返回 true,否则返回 false。
instanceof 运算符在强制类型转换中的作用是:在进行强制类型转换之前,首先判断前一个对象是否是后一个类的实例,是否可以成功转换,从而保证代码的健壮性。
对于如下代码:
// 强制类型转换
Son s = (Son) f;
s.self();
如果不确定 Son 类和 Father 类的关系,直接进行强制类型转换,可能会出现 ClassCastException 异常,使用 instanceof运算符可以让强制类型转换更加安全。
// 更加安全的强制类型转换
if( f instanceof Son)
{
Son s = (Son) f;
}