Java语法学习笔记:多态
概念
向上转型(upcasting)
- 子类型-->父类型
- 自动类型转换
向下转型(downcasting)
- 父类型-->子类型
- 强制类型转换
无论哪一种转型都需要二者之间有继承关系
多态的语法
例子1 -向上转型
public class Animal {
public void move() {
System.out.println("动物在行走");
}
}
public class Cat extends Animal {
public void move() {
System.out.println("猫在走猫步");
}
public void catchMouse() {
System.out.println("猫在捉老鼠");
}
}
public class Bird extends Animal {
public void fly() {
System.out.println("鸟儿在飞翔");
}
}
public class Test {
public static void main(String[] args) {
Animal a = new Cat();
a.move();
}
}
输出:猫在走猫步
如果Cat类中move方法被注释:
输出:动物在行走
说明:
0.a是一个引用,引用是一个变量,a指向一个Cat对象的实例。
1.Java程序永远都分为编译阶段和运行阶段
2.先分析编译阶段,再分析运行阶段,编译无法通过,根本是无法运行的
3.编译阶段编译器检查a这个引用的数据类型为Animal,由于Animal.class字节码当中有move()方法,所以编译通过。这个过程称为静态绑定,编译阶段绑定。
4.在程序运行阶段,JVM堆内存中真实创建的对象是Cat对象,那么程序在运行阶段一定会调用Cat对象的Move方法,此时发生了程序的动态绑定,运行阶段绑定。
5.即使Cat类中重写move方法被注释,在运行期间调用的一定还是Cat类中的move方法,只是这个move方法是从父类Aniaml中继承过来的。
6.父类型引用指向子类型对象这种机制导致程序存在编译阶段绑定和运行阶段绑定两种不同的形态/状态,这种机制可以称为一种多态语法机制。
练习
a.catchMouse();
结果:编译阶段出错
说明:
编译阶段编译器检查到s的类型是Aniaml类型,从Animal.class字节码文件中查找catchMouse()方法而没有找到,导致静态绑定失败,导致编译失败。
例子2 -向下转型
Animal a = new Cat();
Cat c2 = (Cat)a;
c2.catMouse();
输出:猫在捉老鼠
说明:
- a的类型是Animal(父类),转换为Cat(子类),被称为向下转型,是强制类型转换
- 什么时候使用向下转型?
- 当调用的方法是子类型中特有的,在父类型当中不存在,必须进行向下转型。
练习
Animal a3 = new Bird();
Cat c3 = (Cat)a3;
c3.catchMouse();
结果:编译通过,运行异常:java.lang.ClassCastException
说明:
程序编译虽然通过了,但是程序在运行阶段会出现异常,因为JVM堆内存当中真实存在的对象是Bird类型,Bird对象无法转换成Cat对象,因为二者之间不存在任何继承关系,出现了著名的异常:
java.lang.ClassCastException-类型转换异常
这种异常总是在“向下转型”的时候会发生
例子3 -向下转型的异常如何避免?
使用instanceof运算符:
- 语法: (引用 instanceof 数据类型名)
- 例如: (a instanceof Animal)
- 返回:
- true:表示引用a指向的对象是Animal类型
- false:表示引用a指向的对象不是Animal类型
if(a3 instanceof Cat){
Cat c3 = (Cat)a3;
c3.catchMouse();
}else if(a3 instanceof Bird){
Bird b2 = (Bird)a3;
b2.fly();
}
** Java规范中要求:在进行向下转型时,最好使用instanceof运算符进行判断**;
在分工合作的情况下,编写一个方法的程序员可能不知道将来传入这个方法的参数会是什么类型,所以需要使用 instanceof运算符进行判断;
好的程序员要严谨,每一个乡下类型转换,都要使用instanceof运算符进行判断。
多态在实际开发中的应用
以主人喂养宠物为例:
- 主人有喂养的方法
- 宠物有进食的方法
程序1
public class Cat {
public void eat() {
System.out.println("小猫在吃鱼");
}
}
public class Dog {
public void eat() {
System.out.println("小狗在啃骨头");
}
}
public class Master {
public void feed(Cat c) {
c.eat();
}
public void feed(Dog d) {
d.eat();
}
}
public class Test {
public static void main(String[] args){
Cat tom = new Cat();
Dog erha = new Dog();
Master lisi = new Master();
lisi.feed(tom);
lisi.feed(erha);
}
}
分析:
1.如果主人再养新的宠物,那么还需要修改Master类.
2.Master和Cat,Dog类的关联程度高,代码的耦合度高,程序的扩展性差.
程序2-使用多态
class Pet {
public void eat() {
System.out.println("宠物在吃东西");
}
}
class Cat extends Pet{
public void eat() {
System.out.println("小猫在吃鱼");
}
public void catchMouse(){
System.out.println("猫可以捉老鼠");
}
}
class Dog extends Pet{
public void eat() {
System.out.println("小狗在啃骨头");
}
public void helpBlind(){
System.out.println("小狗可以导盲");
}
}
class Master {
//Master类面向的是一个抽象的Pet,不在面向具体的宠物
public void feed(Pet pet) { //父类型的引用
pet.eat();
}
//父类型想要调用子类型特有的方法
public void specialAction(Pet pet){
if(pet instanceof Cat){
Cat c = (Cat)pet;
c.catchMouse();
}else if(pet instanceof Dog){
Dog d = (Dog)pet;
d.helpBlind();
}else{
System.out.println("错误的向下类型转换");
}
}
}
public class Test {
public static void main(String[] args){
Cat tom = new Cat();
Dog erha = new Dog();
Master lisi = new Master();
lisi.feed(tom);
lisi.feed(erha);
//不同动物的特有行为
lisi.specialAction(tom);
lisi.specialAction(erha);
}
}
分析:
通过使用多态向上转型的语法,将子类型的对象赋给父类型的引用,提高了程序的可拓展性。
从上面的例子也说明了方法覆盖的作用;如果没有方法覆盖,那么多态机制就无法使用。
总结多态:
- 多态机制降低了程序的耦合度【解耦合】,提高了程序的扩展力
- 面向抽象编程,面向父类型编程,不要面向具体编程
面向对象的三大特征
封装、继承、多态
- 有了封装,有了整体的概念
- 对象和对象之间产生了继承
- 有了继承之后,才有了方法的覆盖和多态