13.[Java 面向对象] 多态


1. 多态介绍

  • 多态就是方法或对象具有的多种状态

  • 多态是建立在封装和继承基础之上的具体表现有方法的重写跟方法的重载

  • 多态是面向对象的三个基本特征之一

  • 多态的前提是 两个对象(类)存在继承关系

2. 方法的多态

public class Poly01_Method {
    public static void main(String[] args) {
        Base01 base = new Base01();
        Sub01 sub = new Sub01();
        base.say();
        sub.say();
        // 通过传入不同的参数,就会调用不同的 sum 方法
        // 对于这两个方法来说, 就构成方法的多态
        sub.sum(10,20);
        sub.sum(10,20,30);
    }
}

class Base01 {
    // 子类与父类的 say方法构成了重写
    public void say(){
        System.out.println("hello Base01");
    }

    // 子类与父类的 sum方法构成了重载
    public void sum(int i,int j){
        System.out.println(i+" + "+j+" = "+(i+j));
    }
}

class Sub01 extends Base01 {
    public void say() {
        System.out.println("hello Sub01");
    }

    public void sum(int i, int j, int k) {
        System.out.println(i + " + " + j + " + " + k + " = " + (i + j + k));
    }
}

3. 对象的多态

  • 一个对象的类型可以分为编译类型和运行类型
编译类型 对象名 = new 运行类型();
// 编译类型 看 等号 左边
// 运行类型 看 等号 右边
  1. 编译类型在定义对象时就确定了,不能改变
  2. 运行类型是可以变化的
  3. 一个对象的编译类型和运行类型可以不一致
  4. 对象调用方法的时候,调用的是运行类型的方法
Animal animal = new Dog();
// 编译类型是:Animal 
// 运行类型是:Dog 

animal  = new Cat();
// 编译类型是:Animal 
// 运行类型是:Cat
  • 代码示例
public class PolyObject {
    public static void main(String[] args) {
        Animal animal = new Dog();
        // 编译类型是:Animal
        // 运行类型是:Dog
        animal.say(); // 小狗汪汪叫

        animal = new Cat();
        // 编译类型依然是:Animal
        // 运行类型是:Cat
        animal.say(); //小猫喵喵叫
    }
}

class Animal {
    public void say() {
        System.out.println("动物会叫");
    }
}

class Cat extends Animal {
    public void say() {
        System.out.println("小猫喵喵叫");
    }
}

class Dog extends Animal {
    public void say() {
        System.out.println("小狗汪汪叫");
    }
}

4. 多态代码示例

使用多态的方式能编写一个程序, 有主人类Master, 有feed 方法, 可以给不同动物喂不同食物

代码示例中的类关系图

public class Master {
    public static void main(String[] args) {
        Animal dog = new Dog("小狗");
        System.out.println(dog.name); // 编译类型是 Animal, 运行类型是 Dog
        Food dogFood = new DogFood("狗粮"); // 编译类型是 Food, 运行类型是 DogFood
        Master master = new Master();
        master.feed(dog,dogFood);

        Animal cat = new Cat("小猫");
        Food catFood = new CatFood("猫粮");
        master.feed(cat,catFood);

        Animal pig = new Pig("小猪");
        Food pigFood = new PigFood("猪粮");
        master.feed(pig,pigFood);
    }

    public void feed(Animal animal,Food food){
        System.out.println(" 给 "+animal.name+" 喂 "+food.name);
    }
}
class Animal{
    String name;
    public Animal(String name) {
        this.name = name;
    }
}
class Dog extends Animal{
    String name;
    public Dog(String name) {
        super(name);
        this.name = name;
    }
}
class Cat extends Animal{
    String name;
    public Cat(String name) {
        super(name);
        this.name = name;
    }
}
class Pig extends Animal{
    String name;
    public Pig(String name) {
        super(name);
        this.name = name;
    }
}
class Food{
    String name;
    public Food(String name) {
        this.name = name;
    }
}
class DogFood extends Food{
    String name;
    public DogFood(String name) {
        super(name);
        this.name = name;
    }
}
class CatFood extends Food{
    String name;
    public CatFood(String name) {
        super(name);
        this.name = name;
    }
}
class PigFood extends Food{
    String name;
    public PigFood(String name) {
        super(name);
        this.name = name;
    }
}

5. 多态的向上转型

  • 向上转型的本质是 父类的引用 指向了 子类的对象

语法:

编译类型 对象名 = new 运行类型();
  ||     ||           ||
父类类型 引用名 = new 子类类型();
  • 向上转型时

编译类型(父类): 决定了可以调用哪些方法和属性,即:(在遵守访问权限的前提下),可以调用父类中的所有的属性和方法,也就是说无法调用子类中特有的属性和方法。

运行类型(子类):决定了属性和方法调用后的具体执行效果。如果子类没有则执行效果看父类方法。

方法的查找顺序跟继承中的方法查找顺序一致,先从子类(运行类型)中查找, 子类没有则去父类查找

属性没有重写之说,属性的值要看编译类型

向上转型
  • 代码示例
public class Poly03_Upward {
    public static void main(String[] args) {
        Father06 father = new Son06();

        System.out.println(father.v1); // Father - 10
        //System.out.println(father.v2); // 执行类型中无法访问编译类型中的私有属性
        System.out.println(father.v3); // Father - 30
        //System.out.println(father.v4); // 执行类型中无法访问编译类型中的私有属性

        father.m1(); // Son method m1
        father.m2(); // GrandFather method m2
        father.m3(); // Father method m3
//        father.m4(); // 执行类型中无法访问编译类型中的私有方法
    }
}
class GrandFather06{
    public String v1 = "GrandFather - 10";
    public String v2 = "GrandFather - 20";
    private String v3 = "GrandFather - 30";
    private String v4 = "GrandFather - 40";

    public void m1(){ System.out.println("GrandFather method m1"); }
    public void m2(){ System.out.println("GrandFather method m2"); }
    private void m3(){ System.out.println("GrandFather method m3"); }
    private void m4(){ System.out.println("GrandFather method m4"); }
}

class Father06 extends GrandFather06{
    public String v1 = "Father - 10";
    private String v2 = "Father - 20";
    public String v3 = "Father - 30";
    private String v4 = "Father - 40";

    public void m1(){ System.out.println("Father method m1"); }
//    不能缩小父类方法的访问范围
//    private void m2(){ System.out.println("Father method m2"); }
    public void m3(){ System.out.println("Father method m3"); }
    private void m4(){ System.out.println("Father method m4"); }
}

class Son06 extends Father06{
    public String v1 = "Son - 10";
    private String v2 = "Son - 20";
    private String v3 = "Son - 30";
    public String v4 = "Son - 40";

    public void m1(){ System.out.println("Son method m1"); }
//    不能缩小父类方法的访问范围
//    private void m2(){ System.out.println("Son method m2"); }
//    不能缩小父类方法的访问范围
//    private void m3(){ System.out.println("Son method m3"); }
    public void m4(){ System.out.println("Son method m4"); }
}

6. 多态的向下转型

语法:

编译类型 对象名 = (变异类型)运行类型
  ||     ||        ||      ||   
子类类型 引用名 = (子类类型)父类引用

只能强转父类的引用, 不能强转父类的对象

要求父类的引用必须指向的是当前目标类型的对象

当向下转型后, 可以调用子类类型中所有的成员。

public class Poly04_Downward {
    public static void main(String[] args) {
        Father04 father = new Son04();

        Son04 son = (Son04)father;

        System.out.println(son.v1); // Father - 10
//        System.out.println(father.v2); //
//        System.out.println(son.v3); //
//        System.out.println(father.v4); //

        son.m1(); // Son method m1
        son.m2(); // GrandFather method m2
        son.m3(); // Father method m3
        son.m4(); // Son method m4
    }
}
class Son04 extends Father04{
    public String v1 = "Son - 10";
    private String v2 = "Son - 20";
    private String v3 = "Son - 30";
    public String v4 = "Son - 40";

    public void m1(){ System.out.println("Son method m1"); }
    //    不能缩小父类方法的访问范围
//    private void m2(){ System.out.println("Son method m2"); }
//    不能缩小父类方法的访问范围
//    private void m3(){ System.out.println("Son method m3"); }
    public void m4(){ System.out.println("Son method m4"); }
}
class Father04 extends GrandFather04{
    public String v1 = "Father - 10";
    private String v2 = "Father - 20";
    public String v3 = "Father - 30";
    private String v4 = "Father - 40";

    public void m1(){ System.out.println("Father method m1"); }
    //    不能缩小父类方法的访问范围
//    private void m2(){ System.out.println("Father method m2"); }
    public void m3(){ System.out.println("Father method m3"); }
    private void m4(){ System.out.println("Father method m4"); }
}
class GrandFather04{
    public String v1 = "GrandFather - 10";
    public String v2 = "GrandFather - 20";
    private String v3 = "GrandFather - 30";
    private String v4 = "GrandFather - 40";

    public void m1(){ System.out.println("GrandFather method m1"); }
    public void m2(){ System.out.println("GrandFather method m2"); }
    private void m3(){ System.out.println("GrandFather method m3"); }
    private void m4(){ System.out.println("GrandFather method m4"); }
}

7. 多态中的 instanceOf

instanceOf 比较操作符,用于判断对象的 执行类型是否为某类型或某类的子类型

  • 代码示例
public class Poly05_Instanceof {
    public static void main(String[] args) {
        // 编译类型 Sub , 执行类型 Sub
        Sub sub = new Sub();
        System.out.println("sub instanceof Sub = "+(sub instanceof Sub)); // true
        System.out.println("sub instanceof Base = "+(sub instanceof Base)); // false

        // 编译类型 Base , 执行类型 Base
        Base base = new Base();
        System.out.println("base instanceof Sub = "+(base instanceof Sub)); // true
        System.out.println("base instanceof Base = "+(base instanceof Base)); // true

        // 向上转型
        // 编译类型是 Sub,执行类型是 Base
        Sub sub2 = new Base();
        System.out.println(sub.count);
        System.out.println("sub2 instanceof Sub = "+(sub2 instanceof Sub)); //  true
        System.out.println("sub2 instanceof Base = "+(sub2 instanceof Base)); //  true

        // 向下转型
        // 编译类型 Base , 执行类型 Base
        Base base2 = (Base)sub2;
        System.out.println("base2 instanceof Sub = "+(base2 instanceof Sub)); //  true
        System.out.println("base2 instanceof Base = "+(base2 instanceof Base)); //  true
    }
}

class Sub{ // 父类
    int count = 20;
}

class Base extends Sub{ // 子类
    int count = 10;
}

8. 动态绑定机制

  1. 对象的属性没有动态绑定机制,哪里使用就调用哪里的属性
  2. 当调用方法的时候, 方法和运行类型(内存地址)绑定
  • 动态绑定测试代码 1
public class Poly06_DynamicBanding {
    public static void main(String[] args) {
        // 编译类型 Base06, 运行类型 Sub06
        Base06 a = new Sub06();
        System.out.println(a.sum1()); // 40
        System.out.println(a.sum2()); // 30
        System.out.println(a.i); // 10 // 访问属性, 看编译类型
    }
}

class Base06 {
    int i = 10;

    public int sum1(){ return getI()+10; }
    public int sum2(){ return i+10; }
    public int getI() { return i; }
}

class Sub06 extends Base06 {
    int i = 20;

    @Override
    public int sum1(){ return i+20; } // 20+20 = 40
    @Override
    public int sum2(){ return i+10; } // 20+10 = 30
    @Override
    public int getI() { return i; }
}
  • 动态绑定测试代码 1
public class Poly07_DynamicBanding {
    public static void main(String[] args) {
        // 编译类型 Base07, 运行类型 Sub07
        Base07 a = new Sub07();
        System.out.println(a.sum1()); // 30
        System.out.println(a.sum2()); // 20
        System.out.println(a.i); // 10 // 访问属性, 看编译类型
    }
}

class Base07 {
    int i = 10;

    // 动态绑定
    // 这时运行类型为 Sub07,会调用 sub07 中的 getI 方法
    public int sum1(){ return getI()+10; } // 20+10 = 30
    public int sum2(){ return i+10; } // 10+10 = 20
    public int getI() { return i; }
}

class Sub07 extends Base07 {
    int i = 20;

    @Override
    public int getI() { return i; }
}

动态绑定示意图

9. 多态的应用

9.1 多态数组

数组中实际存储元素的类型为数字定义类型的子类 该数组即为多态数组

应用实例:
现在有一个继承结构如下,要求创建一个 Person 对象, 两个 Student 对象和两个Teacher 对象,放在一个数组中, 并调用每个对象的say 方法

如果调用子类特有方法, 比如Teacher 有 teach 方法, Student 类有一个study 方法, 怎么调用?

多态数组
public class Poly08_Array {
    public static void main(String[] args) {
        Person[] persons = new Person[4];
        persons[0] = new Student("小明", 12, 90);
        persons[1] = new Teacher("张老师", 32, 5000);
        persons[2] = new Student("小红", 12, 90);
        persons[3] = new Teacher("李老师", 30, 6000);

        for (int i = 0; i < persons.length; i++) {
            // 编译类型为person , 运行类型会根据实际的执行类型调用不同类的方法
            persons[i].say();
            // 使用 instanceof 判断 persion[i] 的运行类型是否是 Student
            if (persons[i] instanceof Student) {
                ((Student) persons[i]).study();
            }
            // 使用 instanceof 判断 persion[i] 的运行类型是否是 Teacher
            if (persons[i] instanceof Teacher) {
                ((Teacher) persons[i]).teach();
            }
        }
    }
}

class Person {
    public String name;
    public int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public void say() {
        System.out.println("讲话: ");
    }
}

class Student extends Person {
    int i = 20;
    private int score;
    public Student(String name, int age, int score) {
        super(name, age);
        this.score = score;
    }

    public void say() {
        super.say();
        System.out.println(name+" 不懂提问"+ i);
    }

    public void study() {
        System.out.println(name+" 好好学习,天天向上。"+ i);
    }
}

class Teacher extends Person {
    int i = 30;
    private int salary;
    public Teacher(String name, int age, int salary) {
        super(name, age);
        this.salary = salary;
    }

    public void say() {
        super.say();
        System.out.println(name+" 讲课 "+ i);
    }

    public void teach() {
        System.out.println(name+" 教书育人"+ i);
    }
}

9.2 多态参数

传送的实参类型为形参类型的子类, 即为参数的多态

使用示例:

  1. 定义员工类(Employee), 包含姓名 和 月工资(private), 以及计算年工资的方法(getAnnual)

  2. 普通员工类 和 经理类 继承了 员工类

    • 增加经理类特有属性 奖金 (bonus) 和特有方法 管理(manage)

    • 增加普通员工方法工作(work)

    • 普通员工类和经理类都要求重写 getAnnual 方法

  3. 测试类中添加 show(Employee e) 方法 实现获取任何员工的年工资,并在main 方法中调用该方法

  4. 测试类中添加 testWork 方法,如果是普通员工,则调用work 方法, 如果是经理则调用 manage方法

多态参数

public class Poly09_Parameters {
    public static void main(String[] args) {
        Employee tom  = new Worker("tom",1000);
        Employee jerry  = new Manager("jerry",2000,10000);

        Poly09_Parameters ploy = new Poly09_Parameters();
        ploy.show(tom);
        ploy.show(jerry);

        ploy.work(tom);
        ploy.work(jerry);
    }

    public void show(Employee employee){
        System.out.println(employee.getAnnual()); // 动态绑定
    }
    public void work(Employee employee){
        if(employee instanceof  Worker){
            ((Worker) employee).work(); // 向下转型
        }
        if(employee instanceof Manager){
            ((Manager) employee).manage(); // 向下转型
        }
    }
}

class Employee {
    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }

    private String name;
    private double salary;

    public double getAnnual() {
        return salary * 12;
    }
}
class Worker extends Employee{

    public Worker(String name, double salary) {
        super(name, salary);
    }

    public void work(){
        System.out.println("普通员工工作");
    }
    @Override
    public double getAnnual(){
        System.out.println("计算员工年薪");
        return super.getAnnual();
    }
}

class Manager extends Employee{

    private double bonus;

    public Manager(String name, double salary,double bonus) {
        super(name, salary);
        this.bonus = bonus;
    }

    public void manage(){
        System.out.println("管理公司");
    }
    @Override
    public double getAnnual(){
        System.out.println("计算经理年薪");
        return super.getAnnual() + bonus;
    }
}

文章作者: hnbian
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hnbian !
评论
 上一篇
Spark RDD 的介绍 Spark RDD 的介绍
1. 什么是RDD传统的MapReduce 虽然有自动容错,平衡负载和可扩展的优点,但是其最大的缺点是在迭代计算的时候,要进行大量的磁盘IO操作,而RDD正是解决这一缺点的抽象方法。RDD (Resilient Distributed Da
2018-02-02
下一篇 
14.[Java 面向对象] Object 类介绍 14.[Java 面向对象] Object 类介绍
1. Object 类介绍1.1 介绍 Java Object 类是所有类的父类,也就是说 Java 的所有类都继承了 Object,子类可以使用 Object 的所有方法。 Object 类位于 java.lang 包中,编译时会自动导
2018-02-02
  目录