面向对象编程的语言里,多态是一个非常重要的特性,优点很多,类型解耦,灵活性,可替代性,接口性等。面向对象编程的三大特性:封装、继承,多态。封装和继承都很好理解,多态可能你每天都在用,但却不知道怎么解释它,当然,这也是新手从入门到放弃一个必经之路☺

一、多态概念

多态?顾名思义,就是一种行为具有多种形态。再具体一点,就是同一个接口,有多个不同的实现。

要完全理解Java里的多态,还需要知道两个概念:向上转型,向下转型。

二、向上转型

向上转型:子类型转化为父类型,自动类型转化。

定义父类

class Fu {
    void SayHello() {
        System.out.println("Fu Say Hello");
    }
}

定义子类,重写父类方法

class Zi extends Fu {
    @Override
    void SayHello() {
        System.out.println("Zi Say Hello");
    }
}

测试类

public class Main {
    public static void main(String[] args) {
        Fu f = new Zi();
        f.SayHello();   // 输出“Zi Say Hello”
    }
}

Fu f = new Zi(); 父类型的子类实例,父类型引用指向子类型对象

f.SayHello(); 调用父类子类共有的方法

代码很简单,不过我们换个角度来分析它。

  • 编译期:这时因为还没 new 出一个 Zi 类实例,所以编译器认为这是父类,去 Fu.classSayHello 方法,发现有这个方法,所以编译通过,这叫 静态绑定
  • 运行期:运行时,这个时候已经 new 出一个 Zi 类实例,所以 f.SayHello() 调用的是子类的 SayHello 方法,这叫 动态绑定

三、向下转型

向下转型:父类型转化为子类型,强制类型转化

给Zi类再增加一个特有的方法 Cry()

class Zi extends Fu {
    void Cry() {
        System.out.println("Zi crying ......");
    }
}

测试代码

public class Main {

    public static void main(String[] args) {
        Fu f = new Zi();
        f.Cry();  // ClassCastException
    }
}

上面这个测试代码执行会报错,因为调用了子类特有的方法

  • 编译期:还没 new 出 Zi 类实例,执行 f.Cry() 就会去 Fu.class 里查找 Cry 方法,没找到,编译器报 ClassCastException。

所以改一下

public class Main {

    public static void main(String[] args) {
        Fu f = new Zi();
        ((Zi) f).Cry();   
    }
}

((Zi) f).Cry(); 先做强制类型转化,再调用子类特有的方法

  • 编译期:因为做了强制类型转化,所以编译器就会去 Zi.class 里找到 Cry 方法,这样就通过了编译
  • 运行期:运行时,new 出了 Zi 类实例,调用 Zi 类 Cry 方法

更严谨一点的写法,在做强制类型转化前,都用instance判断一下是否存在继承关系

public class Main {

    public static void main(String[] args) {
        Fu f = new Zi();
        if (f instanceof Zi) {
            Zi z = (Zi) f;
            z.Cry();
        }
    }
}

四、多态

多态产生的条件

  1. 继承
  2. 方法覆盖
  3. 向上转型

继承和方法覆盖这个不用说,都能记得住。重点来了,第三点,记住这句话 :【向上转型:父类型引用指向子类型对象】,这样使用父类型引用就能调用到子类型重写的方法了,就能产生多种形态了。

我们结合一个小业务来使用一下多态:主人喂食不同的宠物

定义抽象宠物类

public class Pet {
    public void eat() {}
}

定义狗类,继承 Pet 类

public class Dog extends Pet {
    public void eat() {
        System.out.println("狗吃食了....");
    }
}

定义猫类,继承 Pet 类

public class Cat extends Pet {
    public void eat() {
        System.out.println("猫吃食了....");
    }
}

定义主人类

public class Master {
    public void feed(Pet pet) {
        pet.eat();
    }
}

测试使用

public class Main {

    public static void main(String[] args) {
        Master master = new Master();
        Cat cat = new Cat();
        Dog dog = new Dog();
        // 同一个类型,调用同一个方法,出现不同的形态
        master.feed(cat);  // "猫吃食了...."
        master.feed(dog);  // "狗吃食了...."
    }
}

在上诉示例代码里,Master 类的 feed 方法传入类型为Pet类型的参数,而Pet类就是Dog类和Cat类的父类,使用时传入不同的子类,这样就可以调用到不同的子类方法。

这就是我们在上面说的向上转型,父类型引用指向子类型对象!!!

五、多态的弊端

不能使用子类特有的成员属性和子类特有的成员方法。

所以使用多态时,想用子类型特有的成员方法或属性怎么办呢?

那就要把父类型引用指向子类型的这个东西,再做一次向下转型。

改造一下上面的Cat类,增加特有的成员属性和成员方法

public class Cat extends Pet {
    // 子类特有的成员变量
    public String name;
    public Cat() {
        this.setName();
    }
    public void setName() {
        this.name = "tom";
    }
    public void eat() {
        System.out.println("猫吃食了....");
    }

    // 子类特有的方法
    public static void catchMouse() {
        System.out.println("猫抓到一只老鼠....");
    }
}

测试使用

public class Main {

    public static void main(String[] args) {
        Pet pet = new Cat();
        pet.eat();
        // pet.catchMouse();    // 编译时找不到这个方法
        // System.out.println("pet'name is " + pet.name);   // 编译时找不到这个属性

        // 向下转型为Cat类型
        Cat pc = (Cat)pet;
       // 可以访问子类特有的属性和方法了
        pc.catchMouse();
        System.out.println("pet'name is " + pc.name);
    }
}

向下转型后,就能访问子类特有的属性和方法了,这样做有缺点,不过好处就是更灵活了,不需要在堆内存中再创建一个新的对象了。

所以别人问你多态是什么,知道该怎么回答了吧,别再傻傻的用多种形态来一句话概括了。

文章目录