理解多态
多态,即多种形态,面向对象程序设计语言当中最核心的特征,理解多态能帮助我们更好的进行程序设计。
生活中的多态
我们知道动物一般都有吃东西、跑、跳、叫等等一些通用的行为能力,但是不同动物针对这些行为的表现形式是不同的,比如猫、狗、兔子喜欢吃的东西各有不同,它们的叫声也不一样。
再比如当我们按下键盘中的F1键时,针对当前工作窗口的不同它也将会显示不同的帮助文档,比如在eclipse中弹出的是关于eclipse中的帮助文档,在word中就弹出word的帮助文档。
所以可以看出同样一种行为,在不同的对象上会产生不同的显示结果,这就是生活中的多态。
程序中的多态
在程序设计中,多态就意味着允许不同类的对象对同一消息做出不同的响应。广泛意义上而言,多态可以分为:编译时多态和运行时多态。
编译时多态(设计时多态):表现形式为方法重载。
运行时多态:JAVA运行时系统根据调用该方法的实例的类型来决定选择调用哪个方法。我们平时所说的多态大多指的是运行时多态。
多态存在的必要条件
- 满足继承关系
- 重写方法
- 父类引用指向子类对象
多态实现
定义子父类
定义动物父类及猫、狗子类,类的关系图
父类的定义:
/**
* 动物类
*/
public class Animal {
//属性:昵称、年龄(省略getter和setter)
private String name;
private int month;
//构造方法
public Animal(){
}
public Animal(String name,int month){
this.name=name;
this.month=month;
}
//方法:吃东西
public void eat(){
System.out.println("动物都有吃东西的能力");
}
}
子类的定义:
/**
* 猫
*/
public class Cat extends Animal{
//属性:体重
private double weight;
//构造方法
public Cat(){
}
public Cat(String name,int month,double weight){
super(name,month);
this.weight=weight;
}
//方法:跑动
public void run(){
System.out.println("小猫快乐的奔跑");
}
//方法:吃东西(重写父类方法)
@Override
public void eat() {
System.out.println("猫吃鱼~~");
}
}
/**
* 狗
*/
public class Dog extends Animal {
//属性:性别
private String sex;
//构造方法
public Dog(){
}
public Dog(String name,int month,String sex){
this.setMonth(month);
this.setName(name);
this.setSex(sex);
}
//方法:睡觉
public void sleep(){
System.out.println("小狗有午睡的习惯");
}
//方法:吃东西(重写父类方法)
@Override
public void eat() {
System.out.println("狗吃肉~~");
}
}
向上转型(Upcase)
- 向上转型又叫自动转型、隐式转型。向上转型就是父类引用指向子类实例,是小类型到大类型的转换。
- 向上转型是安全的,因为任何子类都继承并接受了父类的方法。
- 向上转型后可以调用子类重写父类的方法以及父类派生的方法,无法调用子类独有的方法。
- 父类中的静态方法无法被子类重写,所以向上转型之后,只能调用到父类原有的静态方法。
public static void main(String[] args) {
//多态的表现:都使用同一父类对象类型完成同一种行为,表现形式却各不相同
Animal one = new Animal();
Animal two = new Cat();
Animal three = new Dog();
one.eat(); //动物都有吃东西的能力
two.eat(); //猫吃鱼~~
three.eat(); //狗吃肉~~
}
动态绑定
多态的实现可以通过向上转型和动态绑定机制来完成,向上转型实现了将子类对象向上转型为父类类型,而动态绑定机制能识别出对象转型前的类型,从而自动调用该类的方法,两者相辅相成。
绑定:绑定就是将一个方法调用同一个方法所在的类连接到一起就是绑定。绑定分为静态绑定和动态绑定两种。
静态绑定:在程序运行之前进行绑定(由编译器和链接程序完成的),也叫做前期绑定。
动态绑定:在程序运行期间由JVM根据对象的类型自动的判断应该调用哪个方法,也叫做后期绑定。
静态绑定的例子
如有一类Human,它派生出来三个子类Chinese类、American类和British类,三个子类中都重写了父类中的方法speak(),在测试类中用静态绑定的方式调用方法speak()。
这种调用方式是在代码里指定的,编译时编译器就知道谁调用的是Chinese的speak(),谁调用的是American的speak()。
动态绑定的例子
此时, Human类中随机生成Chinese类、American 类和British 类的对象,编译器不能根据代码直接确定调用哪个类中的speak()方法,直到运行时才能根据产生的随机数n的值来确定human[i]到底代表哪一个子类的对象,这样才能最终确定调用的是哪个类中的speak()方法,这就是动态绑定。
向下转型(Downcast)
向下转型是与向上转型相对的概念,它是用子类引用指向父类实例,必须进行强转,所以向下转型又叫做强制类型转换。
向下转型后就可以调用子类特有的方法了,但是必须满足转型条件才能进行强转。
public static void main(String[] args) {
Animal one = new Animal();
Animal two = new Cat();
Animal three = new Dog();
//向下转型
Cat temp = (Cat)two;
temp.eat();
temp.run();
//类型不匹配无法进行转换,运行时出现异常:ClassCastException
//如何避免出现该异常?使用instanceof运算符
Cat temp2 = (Cat)one;
Dog temp3 = (Dog)two;
}
instanceof运算符
- 用来判断对象是否满足某个特定类型实例特征,满足则返回true,一般用于if语句中。
- 实际开发中通常判断某个对象是否是某个类或者其子类的实例,或者是某个接口的实现。
- 通过instanceof运算符可以保证向下转型的安全性,避免出现类型转换的异常,提高代码的健壮性。
类型转换案例
案例1:实现喂宠物的功能,要求如下
喂猫咪:吃完东西后(eat方法),主人会带着去玩线球(playBall方法)
喂狗狗:吃完东西后(eat方法),主人会带着狗狗去睡觉(sleep方法)
实现方案:
在喂养动物的种类比较少时,两种方案均可,但是当我们需要实现更多动物的喂养需求时方案1需要增加非常多的方法,而方案2则只需要修改方法体的内部实现,增加相应动物子类的处理方法即可。
案例2:根据条件决定饲养何种宠物
空闲时间多:养狗狗;空闲时间不多:养猫咪
实现方案
结论
在实际的开发当中,如果需要同一个操作行为针对不同条件(参数)返回不同的实例对象,或者完成不同的操作结果时,就可以使用这种多态的方式实现,类型转换的优势就可以充分体现出来了。
多态总结
- 多态是同一个行为具有多个不同表现形式或形态的能力,意味着允许不同类的对象对同一消息做出不同的响应
- 多态的表现形式(实现方式)为方法重载、方法重写、接口实现
- 多态作用是消除类型之间的耦合关系,提高程序的灵活性和可扩展性