翻译翻译,什么叫多态!

493 阅读9分钟

1. 什么是多态

多态(Polymorphism)是面向对象三大特征之一。

多态是同一个行为具有多个不同表现形式或形态的能力。

先来看一个例子:

  1. 新建一个动物类 Animal
public class Animal {
    // 体重
    private int weight;

    public void eat(){
        System.out.println("动物要吃东西");
    }

    public void sleep() {
        System.out.println("动物要睡觉");
    }
}
  1. 新建一个猫类 Cat,继承动物类,并重写 eat 方法。
public class Cat extends Animal{

    public void eat() {
        System.out.println("猫要吃老鼠");
    }
}

  1. 新建一个猪类 Pig,继承动物类,并重写 eat 方法。
public class Pig extends Animal{
    public void eat() {
        System.out.println("猪要吃草");
    }
}
  1. 测试
// 多态测试类
public class PolymorphismTest {
     public static void main(String[] args) {
        Animal a1 = new Cat();
        a1.eat();
        Animal a2 = new Pig();
        a2.eat();
    }
}

运行结果:

上面的例子就是多态,多态就是同一个行为 eat(),作用在不同的对象上(Cat、Pig),会有不同的表现形式(猫吃老鼠,猪吃草)。 也就是说同一操作作用于不同的对象,可以产生不同的效果。

通过上面的例子我们发现了多态的特性:

    1. 子类继承父类
    1. 子类重写父类方法
    1. 父类引用指向了子类的对象(向上转型)

2. 为什么要用多态

继承这一特性告诉我们如果一些类具有一些相同的特征,可以把这些相同的特征提取出来放到一个父类里面,让其他类去继承。

但是后面随着子类的数量越来越多,父类的某些方法已经不能满足一些子类的需求:例如动物吃东西。

所以其他子类需要重写父类的方法,来满足自己特定的需求。

我们只用编写父类的方法,让子类继承父类即可,不过多关注子类重写方法的代码实现。这样可以大大提高程序的可复用性

子类的重写方法可以被父类调用,这叫向后兼容,可以提高代码的可扩充性可维护性

Animal a1 = new Cat();
a1.eat();
Animal a2 = new Pig();
a2.eat();

例如电脑的 USB 接口,当初设计的时候只是定义了这样一个接口规范,无论是键盘还是鼠标只要遵守这个规范就能使用。

当初设计 USB 接口的时候难道还要考虑鼠标是怎样造出来的?键盘里面有多少个按键吗?难道还要考虑在他们身上都设计一个和电脑一样的 USB 接口才能使用?

如果有几千个外设,那设计电脑的人不得累死?

所以设计电脑的人就只从定义 USB 接口出发,鼠标遵守了这个规范,就能操作电脑系统。键盘遵守了这个规范,就能打字......

所以多态就是子类重写父类方法(相当于子类遵守USB接口规范,实现自己的功能),然后从父类角度出发,父类调用子类重写的方法(相当于连上USB接口,实现不同的功能)。

3. 向上转型和向下转型

java 多态中有两种语法格式:向上转型和向下转型。

3.1 向上转型

向上转型:子类型转换为父类型,就是父类引用指向子类对象。

向上转型是一种自动转换类型,比如猫、猪一定属于动物类。所以他们创建的对象可以赋值(从右向左赋值)给父类的引用,例如:

Animal a1 = new Cat();
Animal a2 = new Pig();

3.2 向下转型

向下转型:父类型转换为子类型,又被叫做强制类型转换。例如:

public class PolymorphicTest {
    public static void main(String[] args) {
        Animal animal = new Cat();
        Cat cat = (Cat)animal;
        cat.eat();
    }
}

运行结果:

有的人可能会说,你不是强制类型转换吗?为什么不能这样写:

public class PolymorphicTest {
  public static void main(String[] args) {
      Animal animal = new Animal();
      Cat cat = (Cat)animal;
      cat.eat();
  }
}

我们看一下运行结果:

Exception in thread "main" java.lang.ClassCastException: com.example.xxl.model.Animal cannot be cast to com.example.xxl.model.Cat
	at com.example.xxl.model.PolymorphicTest.main(PolymorphicTest.java:7)

为什么报错了?

因为你第一次新建个动物对象你根本就不知道它是啥动物,所以强转成猫肯定有问题。就好比科学家发现一颗新的星球,你能直接说它是地球或者火星吗?显然不能。

那为什么这段代码没有报错:

Animal animal = new Cat();
Cat cat = (Cat)animal;
cat.eat();

因为 animal 本身指向的就是猫对象,所以它可以向下转型为 Cat。

总结:

  • 向上转型:父类引用指向子类对象。
  • 向下转型:在向上转型的基础之上,再强制转换为子类对象。

4. instanceof 运算符

我们在做向下转型的时候,可能会遇到这种情况:

Animal animal = new Cat();
Pig pig = (Pig)animal;
pig.eat();

运行结果:

Exception in thread "main" java.lang.ClassCastException: com.example.xxl.model.Cat cannot be cast to com.example.xxl.model.Pig
	at com.example.xxl.model.PolymorphicTest.main(PolymorphicTest.java:8)

为什么会抛出类型转换的异常?

这是因为在向下转型的过程中,类型不兼容。上面例子中 animal 明明指向的是一只猫,你非要转换成一头猪,强扭的瓜不甜啊!

那怎样解决这种问题?

我们可以在做向下类型转换之前先判断一下转换的类型,这就需要用到 instanceof 了,它的的运算结果是布尔类型。

instanceof 运算符语法格式:

(引用类型指向的对象 instanceof 类型)

例如:

// 多态测试类
public class PolymorphicTest {
  public static void main(String[] args) {
      Animal animal = new Cat();
      if(animal instanceof Cat){
          Cat cat = (Cat)animal;
          cat.eat();
      }
      if(animal instanceof Pig){
          Pig pig = (Pig)animal;
          pig.eat();
      }
  }
}

运行结果:

解答:上面的例子中我们用 instanceof 运算符判断 animal 引用指向的对象是不是期望的类型,因为 animal 指向的是一只猫类,所以会执行第一个 if 语句,输出“猫吃老鼠”。

5. 从程序运行层面看多态

我们都知道 java 程序分为编译和运行阶段。

Animal animal = new Cat();
animal.eat();

在编译阶段,animal 的数据类型是 Animal,所以在调用 eat 方法时先去 Animal.class 中找 eat 方法。

当程序运行时,animal 指向了 Cat 类,所以会去调用 Cat 类的 eat 方法,所以执行结果是“猫吃老鼠。”

6. 多态小案例

6.1 电脑连接外部设备

  1. 新建一个电脑外部设备类 Equipment
// 电脑设备类
public class Equipment {
    // 设备名称
    String name = "设备";
    public void start() {
        System.out.println("设备开始启动");
    }

    public void work() {
        System.out.println("设备开始工作了");
    }
}
  1. 新建一个鼠标类 Mouse,继承 Equipment
// 鼠标类
public class Mouse extends Equipment{
    String name = "鼠标";
    public void work() {
        System.out.println("鼠标开始工作了");
    }

    public void move() {
        System.out.println("鼠标开始在电脑窗口移动");
    }
}
  1. 新建一个电脑键盘类 Keyboard,继承 Equipment
// 电脑键盘类
public class Keyboard extends Equipment{
    String name = "键盘";
    public void work() {
        System.out.println("键盘开始工作了");
    }

    public void print() {
        System.out.println("键盘开始打字");
    }
}
  1. 新建一个电脑类
// 电脑类
public class Computer {
    public void connect(Equipment equipment){
        System.out.println("电脑连接上了:" + equipment.name);
        equipment.work();
    }
}
  1. 测试
// 多态测试类
public class PolymorphicTest {
  public static void main(String[] args) {
  Computer computer = new Computer();
    Equipment equipment1 = new Mouse();
    equipment1.name ="鼠标";
    equipment1.start();
    System.out.println("--------------------");
    computer.connect(equipment1);
    Mouse mouse = (Mouse)equipment1;
    mouse.move();
    System.out.println("--------------------");
    Equipment equipment2 = new Keyboard();
    equipment2.name = "键盘";
    computer.connect(equipment2);
    Keyboard keyboard = (Keyboard)equipment2;
    keyboard.print();
  } 
}

运行结果:

结果分析:

  1. Mouse 继承了 Equipment 类,并且重写了 work 方法,所以 computer 对象调用 connect 方法传入 equipment1 对象,调用的是 Mouse类中重写的 work 方法,这就是多态。
  2. move 方法是 Mouse 中独有的方法,要想调用 move 方法,必须向下转型为 Mouse 对象。这是因为向上转型之后,多态不使用子类特有的方法
  3. 下面键盘的例子类比。

6.2 花木兰替父从军

  1. 话说南北朝时期,国家开战,朝廷征兵。花木兰的父亲花弧由于年迈不适合参军,儿子又年幼,可是朝廷冷酷无情,花弧一筹莫展。

我们新建一个 HuaHu 类,花弧有自己的姓名、性别、年龄以及“骑马杀敌”的方法。

// 花弧
public class HuaHu {
    // 姓名
    String name = "花弧";
    // 性别
    String sex = "男";
    // 年龄
    int age = 45;
    // 骑马杀敌方法
    public void kill() {
        System.out.println("花弧可以骑马杀敌!!");
    }
}
  1. 花弧的宝贝闺女花木兰不但武艺超群,而且箭术无双。木兰不想让老父亲难过,决定替父从军。

我们新建一个 HuaMuLan 类,继承 HuaHu 类。花木兰也有自己的姓名、性别、年龄以及“骑马杀敌”的方法。不过花木兰有自己特有的方法:涂胭脂粉。

// 花木兰类
public class HuaMuLan extends HuaHu{
  // 姓名
  String name = "花木兰";
  // 性别
  String sex = "女";
  // 年龄
  int age = 20;

  // 重写他爹骑马杀敌方法
  public void kill() {
      System.out.println("花木兰骑马杀敌,牛逼 plus!!");
  }

  // 自己独有的涂胭脂粉的方法
  public void makeUp() {
      System.out.println("我是花木兰,我爱涂胭脂粉,美美哒!耶耶!");
  }
}
  1. 花木兰女扮男装,替父从军。这就相当于父类的引用(花弧的名字)指向了子类对象(花木兰本人)。这其实就是多态中的向上转型

到了军中,长官:“嘿哥们,做一下自我介绍呗!”

花木兰:“花弧,男,45岁。”

所以在访问子类对象(花木兰)的成员属性(姓名、性别、年龄)时,其实看到的都是花木兰她父亲的名字(花弧)、性别(男)、年龄(45岁)。

当骑马打仗时,花木兰亲自上战场,用自己超强的武艺打败了敌军。

但是这时候,花木兰不能也不敢涂胭脂粉。

我们新建一个测试类:

// 多态测试类
public class PolymorphismTest {
  public static void main(String[] args) {
      HuaHu huaHu = new HuaMuLan();
      System.out.println("姓名:"+huaHu.name);
      System.out.println("年龄:"+huaHu.age);
      System.out.println("性别"+huaHu.sex);
      System.out.println("---------战争开始-----------");
      huaHu.kill();
  }
}

运行结果:

  1. 十二年过去了,边疆战乱平定,花木兰跟随大军胜利还朝。

花木兰恢复了女儿身。别人问她是谁,木兰说:“我叫花木兰,女,今年20岁”。这时候,花木兰终于可以涂胭脂粉了。

这其实就是多态中的向下转型

我们新建一个测试类:

// 多态测试类
public class PolymorphismTest {
  public static void main(String[] args) {
      HuaHu huaHu = new HuaMuLan();
      System.out.println("姓名:"+huaHu.name);
      System.out.println("年龄:"+huaHu.age);
      System.out.println("性别"+huaHu.sex);
      System.out.println("---------战争开始-----------");
      huaHu.kill();
      System.out.println("---------战争结束-----------");
      HuaMuLan huaMuLan = (HuaMuLan)huaHu;
      System.out.println("姓名:"+huaMuLan.name);
      System.out.println("年龄:"+huaMuLan.age);
      System.out.println("性别"+huaMuLan.sex);
      huaMuLan.makeUp();
  }
}

运行结果:

总结:

    1. 向上转型,父类引用调用的方法是子类重写的方法。
    1. 向上转型,父类引用获取的属性是父类中的属性。
    1. 向上转型,父类引用不能获取子类独有的方法。
    1. 向下转型,子类可以获取自己独有的属性和方法。