本文将关注面向对象的第三大要素:多态。文章开始先介绍多态的使用方法,然后介绍为什么使用多态。使用多态之前需要先知道三种技术
- 继承
- 向上转型
- 复写(override)
我们姑且把这三种技术称为多态的三要素吧,继承已经在Java类和对象(二)里面描述过了,这里不再赘述。
向上转型
向上转型就是把子类对象转换成父类对象。因为子类继承自父类,子类对象包含了父类对象的信息,所以可以把子类对象转换为父类对象。
//向上转型示例
class Base{
String name;
String age;
public void doSomeThing(){
}
}
class Derived extends Base{
String another;
}
public class TestClass {
public static void main(String[] args){
Derived d = new Derived();
//向上转型
Base b = d;
b.doSomeThing();
}
}
复写(override)
子类可以复用父类的方法实现,也可以重新实现方法,但是方法签名必须和父类一致,这里的重新实现就是复写。
//复写示例,复写doSomeThing方法
class Base{
public void doSomeThing(){
System.out.println("in Base");
}
}
class Derived extends Base{
public void doSomeThing(){
System.out.println("in Derived");
}
}
使用多态
看下面的多态使用示例
//多态的使用示例
class Base{
public void doSomeThing(){
System.out.println("in Base");
}
}
class Derived extends Base{
public void doSomeThing(){
System.out.println("in Derived");
}
}
public class TestClass {
public static void main(String[] args){
Derived d = new Derived();
Base b = d;
b.doSomeThing();//多态,输出in Derived
}
}
上例中doSomeThing方法被复写了,Derived对象向上转型为Base对象以后,使用Base对象调用doSomeThing方法输出“in Derived”,如果没有复写它的输出应该是“in Base”。多态表达的意思是子类可以通过复写方法拥有自己的定制行为,且可以通过向上转型为父类对象来调用,这样每个子类的调用方式是一致的,但是表现出来的行为却各不一样。
//多态解释
package example;
class Base{
public void doSomeThing(){
System.out.println("in Base");
}
}
class Derived1 extends Base{
public void doSomeThing(){
System.out.println("in Derived1");
}
}
class Derived2 extends Base{
public void doSomeThing(){
System.out.println("in Derived2");
}
}
public class TestClass {
static void doSomeThing(Base b){
//调用形式都一样
b.doSomeThing();
}
public static void main(String[] args){
Derived1 d1 = new Derived1();
Derived2 d2 = new Derived2();
doSomeThing(d1); //输出in Derived1
doSomeThing(d2); //输出in Derived2
}
}
看TestClass的doSomeThing方法,不管是对象d1还是d2调用形式都是b.doSomeThing,但是调用的结果却不一样。注意如果Derived1和Derived2没有复写doSomeThing方法,上例中两次调用doSomeThing(Derived)的输出都将是in Base。
为什么使用多态
假设我们在开发一个游戏,实现移动一群NPC(非玩家角色 Non-Player Character )的需求,每个NPC都继承自Spirit类,且每个NPC移动的动画不一样,在不使用多态的情况下怎么实现这种需求?一种实现方式如下:
class Spirit{
//回血
public void plusBlood(){
}
}
//乔峰
class Qiaofeng extends Spirit{
public void move(){
System.out.println("乔峰移动动画");
}
}
//段誉
class Duanyu extends Spirit{
public void move(){
System.out.println("段誉移动动画");
}
}
//
//......还有很多NPC
//
public class TestClass {
public static void main(String[] args){
Qiaofeng qiaofeng = new Qiaofeng();
Duanyu duanyu = new Duanyu();
//......这里还有其它n个NPC
qiaofeng.move();
duanyu.move();
//....每个NPC调用move方法
}
}
可以看到我们需要实例化每一个NPC对象,然后对每个对象调用move方法,这种实现方式的问题在于如果NPC数量很多的时候代码量将非常庞大。如果使用多态将减少很多代码,使用多态后的代码如下:
//多态实现方式
class Spirit{
//回血
public void plusBlood(){
}
public void move(){
System.out.println("通用移动动画");
}
}
//乔峰
class Qiaofeng extends Spirit{
public void move(){
System.out.println("乔峰移动动画");
}
}
//段誉
class Duanyu extends Spirit{
public void move(){
System.out.println("段誉移动动画");
}
}
//
//......假设还有很多NPC
//
public class TestClass {
static void move(ArrayList<Spirit> spirits){
for (Spirit s:spirits) {
s.move();
}
}
public static void main(String[] args){
ArrayList<Spirit> spirits = new ArrayList<>();
spirits.add(new Qiaofeng());
spirits.add(new Duanyu());
//......这里还有其它n个NPC
move(spirits);
}
}
我们把move方法移动到Spirit类里面,子类复写move方法。同样的我们需要实例化每一个对象,不同的是我们把每个对象放到容器spirits里面,然后遍历容器调用move方法。多态实现不用每个对象都调用move方法,减少了调用move方法的代码量。通过比较可以总结出多态有两个优点
- 合理的多态使用可以减少代码量
- 抽象编程
减少代码量的优点在例子中可以明显的看到,抽象编程则不容易看到,我们将在下一篇文章中学习抽象编程。
最后
多态包含三个要素:继承、向上转型、复写。使用多态可以使用抽象编程,可以减少代码量,我相信没有程序员愿意多写代码,特别是重复无聊的代码。
![](https://www.shaoshuidashi.com/wp-content/uploads/2019/04/qrcode_for_gh_649c6e54f6a1_344-300x300.jpg)