多态polymorphic
【基本介绍】
方法或对象具有多种形态,是面向对象的第三大特征,多态是建立在封装和继承基础之上的。
具体体现:
-
方法的多态:重写和重载就体现多态
-
对象的多态(核心,困难,重点)
重要的几句话:
(1)一个对象的编译类型和运行类型可以不一致
(2) 编译类型在定义对象时,就确定了,不能改变
(3) 运行类型是可以变化的
(4) 编译类型看定义时 =号的左边,运行类型看 =号的右边
案例:Animal animal = new Dog();
(Dog类继承于Animal类)
(animal编译类型是Animal,运行类型Dog)
【初识例子】
说明:Dog和Cat是Animal的子类,Bone和Fish是Food的子类
【多态的细节】
细节一:
多态的前提是:两个对象(类)存在继承关系。
多态的向上转型
(1)本质:父类的引用指向了子类的对象
(2)语法:父类类型 引用名 = new 子类类型();
(3)特点:编译类型看左边,运行类型看右边。
可以调用父类中的所有成员(需遵守访问权限);
不能调用子类中特有成员;【在编译阶段,能调用哪些成员,由编译类型来决定的】【向下转型后可以】
最终运行效果看子类的具体实现。
若有对象:Aniaml animal = new Cat();
animal.catchMouse(); //编译报错
【编译类型】则该对象的编译类型是Animal,可以调用父类的sleep等方法,但是不能调用Cat类的catchMouse方法。不能调用的原因是编译器不通过,因为其编译类型是Animal,Animal类没有该方法
animal.eat();
animal.run();
animal.show();
animal.sleep(); //都可以运行
【运行类型】该对象的运行类型是Cat,所以当运行时,它还是先从Cat类中寻找这四个方法,找不到再往上级父类找,然后调用
细节二:
多态的向下转型
(1)语法:子类类型 引用名 = (子类类型)父类引用
Cat cat = (Cat)animal;
cat.catchMouse(); //可以运行
(2)只能强转父类的引用,不能强转父类的对象
(3)要求父类的引用必须指向的是当前目标类型的对象
Dog dog = (Dog)animal; //出现异常,如下图,它本该是只猫
(4)当向下转型后,可以调用子类类型中所有的成员
相当于说,现在有两个引用指向这个cat对象,一个是父类的引用,一个是子类的引用
细节三:
属性没有重写之说,属性的值看编译类型
instanceof 比较修饰符,用于判断对象的运行类型是否为XX类型或XX类型的子类型,运算的结果是true或false
【练习】
【Java的动态绑定机制(非常非常重要)Dynamic binding】
-
当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
-
当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用
由代码可得,对象a的编译类型是 A ,运行类型是 B
所以a.sum()和a.sum1()都从运行类型 B 开始找。
若仅仅把子类的sum()方法注释掉,则此时的输出为 30 ,30
(运行时找到父类的sum方法,但是里面又包含一个getI方法,这个方法父类和子类都设置有;
根据动态绑定机制,这个getI方法会调用运行类型的该方法。)
若仅仅把子类的sum1()方法注释掉,则此时的输出为 40 ,20
(运行时找到父类的sum1方法,但是里面有一个属性i,这个属性父类的子类都设置有;
根据动态绑定机制,这个i属性会调用此方法所在的类的属性。)
【多态数组】
数组的定义类型为父类类型,里面保存的实际元素类型为子类类型
【多态参数】
方法定义的形参类型为父类类型,实参类型允许为子类类型
public void showEmpAnnual(Employee e) {
System.out.println("该员工"+e.getName()+"的年工资为:"+e.getAnnual());
}
public void testWork(Employee e) {
if(e instanceof Worker) {
Worker worker = (Worker)e; //向下转型,调用子类的方法
worker.work();
}else if(e instanceof Manager) {
((Manager)e).manage(); //向下转型,调用子类的方法
}
}