1、定义:
多态(Polymorphism)属于面向对象三大特征之一,它的前提是封装形成独立体,独立体之间存在继承关系,从而产生多态机制。多态是同一个行为具有多个不同表现形式或形态的能力。
现实中,比如我们按下 F1 键这个动作:
● 如果当前在 Flash 界面下弹出的就是 AS 3 的帮助文档;
● 如果当前在 Word 下弹出的就是 Word 帮助;
● 如果当前在 Windows 下弹出的就是 Windows 帮助和支持。
多态就是“同一个行为”发生在“不同的对象上”会产生不同的效果。
2、多态重点:
1、一个对象的编译类型和运行类型可以不一致。
2、编译类型再定义对象时就确定了,不可更改;运行类型可以变化。
3、编译类型看 = 号左边,运行类型看 = 右边。
3、转型:前提是有继承关系。
在java中允许这样的两种语法出现:
1、一种是向上转型(Upcasting):向上转型是指子类型转换为父类型,又被称为自动类型转换。
2、一种是向下转型(Downcasting):向下转型是指父类型转换为子类型,又被称为强制类型转换。
4、运行和编译:
Animal a1 = new Cat();
a1.move();
java程序包括编译和运行两个阶段,分析java程序一定要先分析编译阶段,然后再分析运行阶段,
编译阶段:
编译器只知道a1变量的数据类型是Animal,那么此时编译器会去Animal.class字节码中查找move()方法,发现Animal.class字节码中存在move()方法,然后将该move()方法绑定到a1引用上,编译通过了,这个过程我们可以理解为“静态绑定”阶段完成了。
运行阶段:
紧接着程序开始运行,进入运行阶段,在运行的时候实际上在堆内存中new的对象是Cat类型,也就是说真正在move移动的时候,是Cat猫对象在移动,所以运行的时候就会自动执行Cat类当中的move()方法,这个过程可以称为“动态绑定”。
但无论是什么时候,必须先“静态绑定”成功之后才能进入“动态绑定”阶段。
Animal a = new Cat();
a.catchMouse(); // 报错
为什么会编译报错呢?
那是因为“Animal a = new Cat();”在编译的时候,编译器只知道a变量的数据类型是Animal,也就是说它只会去Animal.class字节码中查找catchMouse()方法,结果没找到,自然“静态绑定”就失败了,编译没有通过。就像以上描述的错误信息一样:在类型为Animal的变量a中找不到方法catchMouse()。
因此:向上转型不能调用子类的特有成员。因为在编译阶段,能调用哪些成员,是由编译类型来决定的。
4.1ClassCastException:类转换异常:
public static void main(String[] args) {
Animal a = new Bird();
Cat c = (Cat)a;
}
以上代码可以编译通过吗?
答案是可以的,为什么呢?那是因为编译器只知道a变量是Animal类型,Animal类和Cat类之间存在继承关系,所以可以进行向下转型(前面提到过,只要两种类型之间存在继承关系,就可以进行向上或向下转型),语法上没有错误,所以编译通过了。但是运行的时候会出问题。
以上的异常是很常见的ClassCastException,翻译为类型转换异常,这种异常通常出现在向下转型的操作过程当中,当类型不兼容的情况下进行转型出现的异常,之所以出现此异常是因为在程序运行阶段a引用指向的对象是一只小鸟,然后我们要将一只小鸟转换成一只猫,这显然是不合理的,因为小鸟和猫之间是没有继承关系的。为了避免这种异常的发生,建议在进行向下转型之前进行运行期类型判断,这就需要我们学习一个运算符了,它就是instanceof。
instanceof运算符的运算结果是布尔类型,可能是true,也可能是false,假设(c instanceof Cat)结果是true则表示在运行阶段c引用指向的对象是Cat类型,如果结果是false则表示在运行阶段c引用指向的对象不是Cat类型。有了instanceof运算符,向下转型就可以这样写了:
Animal a = new Bird();
if(a instanceof Cat){
Cat c = (Cat)a;
c.catchMouse();
}
以上程序运行之后不再发生异常,并且什么也没有输出,那是因为if语句的条件并没有成立,因为在运行阶段a引用指向的对象不是Cat类型,所以(a instanceof Cat)是false,自然就不会进行向下转型了,也不会出现ClassCastException异常了。在实际开发中,java中有这样一条默认的规范需要大家记住:在进行任何向下转型的操作之前,要使用instanceof进行判断,这是一个很好的编程习惯。就像下面的代码:
public static void main(String[] args) {
Animal a = new Bird();
if(a instanceof Cat){
Cat c = (Cat)a;
c.catchMouse();
}else if(a instanceof Bird){
Bird b = (Bird)a;
b.sing();
}
}
5、向上转型:父类的引用指向子类的对象。
当我们需要多个同父的对象调用某个方法时,通过向上转换后,则可以确定参数的统一.方便程序设计。
向上转型:父类的引用指向子类的对象。
Animal animal = new Cat()
注意点:
1、可以调用父类的所有成员,但是要遵守访问权限。
2、不能调用子类的特有成员。因为在编译阶段,能调用哪些成员,是由编译类型来决定的。
即由 animal 来决定。
3、但最终的运行效果看子类的具体实现。即调用方法时,按照从子类开始查找的规则,子类有同名方法,调用子类,子类没有,往上找。
向上转型不能调用子类的方法,编译不通过,但是运行的时候如果子类有和父类同名的方法,那么则运行子类重写的父类方法。
PS:向上转型时,父类只能调用父类方法或者子类覆写后的方法,而子类中的单独方法则是无法调用的.
6、向下转型: 将原来指向子类对象的父类引用(animal),转换为指向子类对象的子类引用(cat)。
类似于基本数据类型的强转
(Son)father;
(int)1.1; // 类似于基本数据类型的强转。
在java中,向下转型则是为了,通过父类强制转换为子类,从而来调用子类独有的方法(向下转型,在工程中很少用到)。
为了保证向下转型的顺利完成,在java中提供了一个关键字:instanceof,通过instanceof可以判断某对象是否是某类的实例,如果是则返回true,否则为false。
向下转型:子类类型 引用名 = (子类类型) 父类引用;
Cat cat = (Cat) animal;
注意点:
1、只能强转父类的引用,不能强转父类的对象。 Cat cat = (Cat) Animal; // 错
2、要求父类的引用必须指向当前目标类的对象:就是先有向上转型才能向下转型。
3、向下转型后,可以调用子类中所有成员。
如果要调用子类的特有方法,那么就要向下转型。
instanceOf:比较操作符:
用于判断对象的运行类型是否是某个类或者是某个类的子类。
package com.hsf.extends_;
public class Instance_ {
public static void main(String[] args) {
BB bb = new BB();
System.out.println(bb instanceof BB); // true
System.out.println(bb instanceof AA); // true
/*
* aa的运行类型是BB,因此
* (aa instanceof AA):BB是AA的子类型? true
* (aa instanceof BB): BB是BB的类型吗? true
* */
AA aa = new BB();
System.out.println(aa instanceof AA); // true
System.out.println(aa instanceof BB); // true
}
}
class AA {}
class BB extends AA {}
运行和编译类型注意点:
1、访问属性看编译类型,访问方法看运行类型。
package com.hsf.extends_.text;
public class Test {
public static void main(String[] args) {
Son son = new Son();
Father father = son;
/*
* 1、访问属性看编译类型:
* son的编译类型是Son,因此访问count去Son里面找, son的count =20。
* father的编译类型是Father,因此访问count去Father里面找,father的count =10。
* */
System.out.println(son.count); // 20
System.out.println(father.count); // 10
/*
* 2、访问方法看运行类型:
* father运行类型是Son,因此去Son里面找M1方法。
* */
father.M1(); // 我是子类M1方法
/*
* 3、==:是比较地址值:只要不是new,那么两个地址值就一样,如果:
* Father father = new Son();那么就是FALSE,因为new了一个新空间。
* */
System.out.println(son == father); // true
}
}
```