Java面向对象三大特征——多态
一、引言
Java面向对象的三大特征:继承、封装、多态。其中继承和封装我们平时用的做广泛,但是多态用到的却不多,这是因为多态是一种建立在不同设计模式基础之上的特征。要使用多态,需要先掌握几个常见的设计模式。在讲解多态之前先普及一下Java变量类型的划分,这有助于我们理解多态。
在Java语言中,根据定义变量位置的不同,可以将变量分成两大类:成员变量和局部变量,他们的区分图如下:
这里要重点讲解一下引用类型的变量。引用变量有两个类型:一个是编译时类型,一个是运行时类型。编译时类型由声明该变量时使用的类型决定;运行时类型由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,就可能出现所谓的多态,下面是一个例子:
二、多态性
先上一段代码:
class Persion{
public String name = "小武";
public void base(){
System.out.println("父类的普通方法");
}
public void test(){
System.out.println("父类的被覆盖的方法");
}
}
class Man extends Persion{
public Object name = "灵灵";
@Override
public void test() {
System.out.println("子类的覆盖父类的方法");
}
public void say() {
System.out.println("子类的普通方法");
}
public staticvoid main(String[] args) {
//编译时类型和运行时类型完全一样,因此不不存在多态
Persion p =new Persion();
System.out.println(p.name);
p.base();
p.test();
//编译时类型和运行时类型完全一样,因此不不存在多态
Man m =new Man();
System.out.println(m.name);
m.test();
m.say();
//下面编译时类型和运行时类型不一样,多态发生
Persion pm =new Man();
//输出小武——这说明访问的是父类的变量
System.out.println(pm.name);
//下面调用将执行从父类继承到的base方法
pm.base();
//下面调用将执行子类的test方法
pm.test();
//因为编译时类型是父类,父类没有提供say方法,所以下面代码编译时会出错
// pm.say();
}
}
第三个引用变量比较特殊,它的编译时类型是Persion,而运行时类型是Man,当调用该引用变量的test方法(子类重写了父类的这个方法)时,实际执行的是子类中覆盖后的test方法,这就可能出现多态了。
因为子类其实是一种特殊的父类,因此Java允许吧一个子类对象直接赋给一个父类引用变量,无需任何类型转换,或者被称为向上转型,向上转型由系统自动完成。
当一个子类对象直接赋给谷类引用变量时,当运行时调用该引用变量的方法时,其方法行为总是表现出子类方法的行为特征,而不是父类方法的行为特征,这就可能出现:相同类型的变量、调用同一个方法时呈现出多种不同的行为特征,这就是多态。
与方法不同的是,对象的Field则不具备多态性。比如上面的pm引用变量,程序输出它的name时,并不是输出子类里定义的实例变量,而是输出父类的实例变量。即通过引用变量来访问其包含的实例变量时,系统总是视图访问它编译时类型所定义的变量,而不是他运行时类型所定义的变量。
注意:引用变量在编译阶段只能调用其编译时类型所具有的方法,但运行时则执行它运行时类型所具有的方法。因此,编写Java代码时,引用变量只能调用声明该变量时所用类里包含的方法。例如,通过Objectp = new Persion()代码定义一个变量p,则这个p只能调用Object类的方法,而不能调用Persion类里定义的方法。
三、引用变量的强制类型转换
看到这里大家可能有这样的疑惑:引用变量运行时不能调用编译时类型所具有的方法,那么其编译时类型的方法是否还存在呢?如果存在怎样调用呢?
答案是存在的,我们可以通过强制类型转换来调用其编译时类型的方法。强制类型转换的知识这里就不在讲解了,下面只给出上面程序中引用变量调用编译时类型所具有的方法的例子:
Persion pm =new Man();
Man m1 = (Man)pm;
m1.test();
m1.say();
四、instanceof运算符
说道强制类型转换,不得不提到instanceof这个运算符,它用来判断前面的对象是否是后面的类或者其子类、实现类的实例。instanceof前面跟一个引用类型变量,后面跟一个类(也可以是接口)。
在使用instanceof运算符时需要注意:instanceof运算符前面操作数的编译时类型要么与后面的类相同,要么与后面的类具有父子继承关系,否则会引起变异错误。下面程序示范了instanceof运算符的用法:
public staticvoid main(String[] args) {
Object a ="小武";
System.out.println((ainstanceof Object) +"");
System.out.println((ainstanceof String) +"");
System.out.println((ainstanceof Math) +"");
System.out.println((ainstanceof Persion) +"");
Man man =new Man();
System.out.println((maninstanceof Persion) +"");
}