讲解Java多态(非常详细)

面向对象编程-多态

1. 先看一个问题

请编写一个程序,Master类中有一个feed(喂食)方法,可以完成主人给动物喂食物的信息。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EwzQMNDa-1676621407162)(photo/image-20230210190613742.png)]

使用传统的方法来解决(private 属性)

传统的方法带来的问题是什么? 如何解决?

问题是: 代码的复用性不高,而且不利于代码维护

解决方案: 引出我们要讲解的多态

代码:

package com.yujianedu.ploy;

public class Animal {
    private String name;
    public Animal(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
package com.yujianedu.ploy;

public class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }
}
package com.yujianedu.ploy;

public class Cat extends Animal {
    public Cat(String name) {
        super(name);
    }
}
package com.yujianedu.ploy;

public class Food {
    private String name;

    public Food(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
package com.yujianedu.ploy;

public class Bone extends Food {
    public Bone(String name) {
        super(name);
    }
}
package com.yujianedu.ploy;

public class Fish extends Food {
    public Fish(String name) {
        super(name);
    }
}
package com.yujianedu.ploy;

public class Master {
    private String name;

    public Master(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    //主人给小狗 喂食 骨头
    public void feed(Dog dog, Bone bone) {
        System.out.println("主人 " + name + " 给 " + dog.getName() + " 吃 " + bone.getName());
    }
    //主人给 小猫喂 黄花鱼
    public void feed(Cat cat, Fish fish) {
        System.out.println("主人 " + name + " 给 " + cat.getName() + " 吃 " + fish.getName());
    }
    //如果动物很多,食物很多
    //===> feed 方法很多,不利于管理和维护
    //Pig --> Rice
    //Tiger ---> meat ...
    //...
}

主程序:

package com.yujianedu.ploy;

public class Ploy01 {
    public static void main(String[] args) {
        Master tom=new Master("汤姆");
        Dog dog=new Dog("大黄");
        Bone bone=new Bone("大棒骨");
        tom.feed(dog,bone);
        Cat cat=new Cat("小花猫");
        Fish fish =new Fish("黄花鱼");
        tom.feed(cat,fish);
    }
}

执行结果:

主人 汤姆 给 大黄 吃 大棒骨
主人 汤姆 给 小花猫 吃 黄花鱼

2. 多[多种]态[状态]基本介绍

方法或对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承基础之上的。

3. 多态的具体体现

1. 方法的多态 PloyMethod.java

重写和重载就体现多态 [案例说明]

package com.yujianedu.ploy;

public class PloyMethod {
    public static void main(String[] args) {
        //方法重载体现多态
        A a = new A();
        //这里我们传入不同的参数,就会调用不同 sum 方法,就体现多态
        System.out.println(a.sum(10, 20));
        System.out.println(a.sum(10, 20, 30));
        //方法重写体现多态
        B b = new B();
        a.say();
        b.say();
    }
}

class B { //父类
    public void say() {
        System.out.println("B say() 方法被调用...");
    }
}
class A extends B {//子类
    public int sum(int n1, int n2){//和下面 sum 构成重载
        return n1 + n2;
    }
    public int sum(int n1, int n2, int n3){
        return n1 + n2 + n3;
    }
    @Override
    public void say() {
        System.out.println("A say() 方法被调用...");
    }
}

执行结果:

30
60
A say() 方法被调用...
B say() 方法被调用...

2. 对象的多态 (核心,困难,重点)

老韩重要的几句话(记住):
(1)一个对象的编译类型和运行类型可以不一致

(2)编译类型在定义对象时,就确定了,不能改变

(3)运行类型是可以变化的.

(4)编译类型看定义时=号的左边,运行类型看=号的右边

案例:com.hspedu.poly.objpoly:PolyObject.java

Animal animal =new Dog);【animal 编译类型是Animal,运行类型Dog】

animal=new Cat();【animal 的运行类型变成了 Cat,编译类型仍然是Animal】

代码:

package com.yujianedu.ploy.objectpoly_;

public class Animal {

    public void cry() {
        System.out.println("Animal cry() 动物在叫....");
    }
}
package com.yujianedu.ploy.objectpoly_;

public class Cat extends Animal {

    @Override
    public void cry() {
        System.out.println("Cat cry() 小猫喵喵叫...");
    }
}
package com.yujianedu.ploy.objectpoly_;

public class Dog extends Animal {

    @Override
    public void cry() {
        System.out.println("Dog cry() 小狗汪汪叫...");
    }
}
package com.yujianedu.ploy.objectpoly_;

public class PolyObject {
    public static void main(String[] args) {
        //体验对象多态特点
        //animal 编译类型就是 Animal , 运行类型 Dog
        Animal animal = new Dog();
        //因为运行时 , 执行到改行时,animal 运行类型是 Dog,所以 cry 就是 Dog 的 cry
        animal.cry(); //小狗汪汪叫
        //animal 编译类型 Animal,运行类型就是 Cat
        animal = new Cat();
        animal.cry(); //小猫喵喵叫
    }
}

执行结果:

Dog cry() 小狗汪汪叫...
Cat cry() 小猫喵喵叫...

4. 多态快速入门案例

使用多态的机制来解决主人喂食物的问题,走代码。 Poly01.java

package com.yujianedu.ploy;

public class Master {
    private String name;

    public Master(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    //使用多态机制,可以统一的管理主人喂食的问题
    //animal 编译类型是 Animal,可以指向(接收) Animal 子类的对象
    //food 编译类型是 Food ,可以指向(接收) Food 子类的对象
    public void feed(Animal animal, Food food) {
        //当传入的是一个Dog和一个Bone
        //相当于 Animal dog = new Dog("小黄");
        //Food bone = new Bone("花骨头");
        System.out.println("主人 " + name + " 给 " + animal.getName() + " 吃 " + food.getName());
    }
}

5. 多态注意事项和细节讨论

com.hspedu.poly_.detail_ 包 : PolyDetail.java

  • 多态的前提是两个对象(类)存在继承关系
  • 多态的向上转型
  1. 本质:父类的引用指向了子类的对象

  2. 语法:父类类型 引用名 = new 子类类型;

  3. 特点:

    • 编译类型看左边,运行类型看右边。

    • 可以调用父类中的所有成员(需遵守访问权限)

    • 不能调用子类中特有成员;

      • 不能调用到子类的字段
      • 不能调用子类特有的方法(假如子类重写了父类的方法,在运行的时候会去调用子类的重写的方法)
    • 最终运行效果看子类的具体实现!

  • 多态向下转型

    1)语法:子类类型 引用名=(子类类型)父类引用;

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

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

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

package com.yujianedu.ploy.detail_;

public class Animal {
    String name = "动物";
    int age = 10;

    public void sleep() {
        System.out.println("睡");
    }

    public void run() {
        System.out.println("跑");
    }

    public void eat() {
        System.out.println("吃");
    }

    public void show() {
        System.out.println("hello,你好");
    }
}
package com.yujianedu.ploy.detail_;

public class Cat extends Animal {

    @Override
    public void eat(){//方法重写
        System.out.println("猫吃鱼");
    }
    public void catchMouse(){//Cat 特有方法
        System.out.println("猫抓老鼠");
    }
}
package com.yujianedu.ploy.detail_;

public class Dog extends Animal {//Dog 是 Animal 的子类}

package com.yujianedu.ploy.detail_;

public class PoluDetail {
    public static void main(String[] args) {
        //向上转型: 父类的引用指向了子类的对象
        //语法:父类类型引用名 = new 子类类型();
        Animal animal = new Cat();
        Object obj = new Cat();//可以吗? 可以 Object 也是 Cat 的父类

        //向上转型调用方法的规则如下:
        //(1)可以调用父类中的所有成员(需遵守访问权限)
        //(2)但是不能调用子类的特有的成员
        //(#)因为在编译阶段,能调用哪些成员,是由编译类型来决定的
        //animal.catchMouse();错误
        //(4)最终运行效果看子类(运行类型)的具体实现, 即调用方法时,按照从子类(运行类型)开始查找方法
        //,然后调用,规则我前面我们讲的方法调用规则一致。
        animal.eat();//猫吃鱼.. animal.run();//跑
        animal.show();//hello,你好
        animal.sleep();//睡


        //老师希望,可以调用 Cat 的 catchMouse 方法
        //多态的向下转型
        //(1)语法:子类类型 引用名 =(子类类型)父类引用;
        //问一个问题? cat 的编译类型 Cat,运行类型是 Cat
        Cat cat = (Cat) animal;
        cat.catchMouse();//猫抓老鼠
        //(2)要求父类的引用必须指向的是当前目标类型的对象(就是要使用向下转型,就要保证被强转的类型之前的运行类型是强转的类型)
        //意思就是:之前的animal对象是指向Cat对象的
        Dog dog = (Dog) animal; //可以吗?
        System.out.println("ok~~");
    }
}

执行结果:

猫吃鱼
hello,你好
睡
猫抓老鼠
ok~~
  • 属性没有重写之说!属性的值看编译类型 PolyDetail02.java
package com.yujianedu.ploy.detail_;

public class PolyDetail02 {
    public static void main(String[] args) {
        //属性没有重写之说!属性的值看编译类型
        Base base = new Sub();//向上转型
        System.out.println(base.count);// ? 看编译类型 10
        Sub sub = new Sub();
        System.out.println(sub.count);//? 20
    }
}
class Base { //父类
    int count = 10;//属性
}

class Sub extends Base {//子类
    int count = 20;//属性
}

执行结果:

10
20
  • instanceOf 比较操作符,用于判断对象的运行类型是否为 XX 类型或 XX 类型的子类型【举例说明】PolyDetail03.java
package com.yujianedu.ploy.detail_;

public class PolyDetail03 {
    public static void main(String[] args) {
        BB bb = new BB();
        System.out.println(bb instanceof BB);// true
        System.out.println(bb instanceof AA);// true
        //aa 编译类型 AA, 运行类型是 BB
        //BB 是 AA 子类
        AA aa = new BB();
        System.out.println(aa instanceof AA);
        System.out.println(aa instanceof BB);
        Object obj = new Object();
        System.out.println(obj instanceof AA);//false
        String str = "hello";
        //System.out.println(str instanceof AA);
        System.out.println(str instanceof Object);//true
    }
}

class AA {} //父类
class BB extends AA {}//子类

执行结果:

true
true
true
true
false
true

6. 课堂练习

请说出下面的每条语言,哪些是正确的,哪些是错误的,为什么? 2min 后老师评讲

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cqlVKxHJ-1676621407163)(photo/image-20230210231814573.png)]

PolyExercise02.java 3min

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0YUWSC8q-1676621407163)(photo/image-20230210234728997.png)]

7. java 的动态绑定机制==(非常非常重要.)==

Java 重要特性: 动态绑定机制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y6TQNzLM-1676621407164)(photo/image-20230211084216141.png)]

Java动态绑定:在上面图中的右下角已经说明什么是动态绑定机制

下面按照顺序再来解释下:

  1. 当一个类B继承了类A,并且类B向上转型创建了一个对象a
    • A a = new B();//向上转型
  2. 类A是对象a的编译类型,类B是对象a的运行类型,(a不能去调用B类特有的方法,只能调用继承了A类的方法),当对象a去调用一个方法get()时,会先去判断运行类型B中是否有该方法,假如没有该对象方法,就会去父类A中寻找是否存在get()方法
  3. 假如父类A中存在get()方法就会去调用该方法
  4. 假如在调用get()方法时,get()方法中又存在一个sum()方法,这时还是会先去运行类型B中先去寻找是否有该方法,假如有该方法就调用,假如没有,老套路,又会去父类A中寻找是否存在该方法,存在就调用返回.

  1. 但是假如a对象调用方法时,在方法体重调用对象属性时,就不会触发动态绑定,你在哪里声明,就在哪里使用

代码如下:

package com.yujianedu.ploy.dynamic_;

public class DynamicBinding {
    public static void main(String[] args) {
        //a 的编译类型 A, 运行类型 B
        A a = new B();//向上转型
        System.out.println(a.sum());//?40 -> 30
        System.out.println(a.sum1());//?30-> 20
    }
}

class A {//父类
    public int i = 10;

    //动态绑定机制:
    public int sum() {//父类 sum()
        return getI() + 10;//20 + 10
    }

    public int sum1() {//父类 sum1()
        return i + 10;//10 + 10
    }

    public int getI() {//父类 getI
        return i;
    }
}

class B extends A {//子类
    public int i = 20;

    // public int sum() {
    // return i + 20;
    // }
    @Override
    public int getI() {//子类 getI()
        return i;
    }
    // public int sum1() {
    // return i + 10;
    // }
}

执行结果:

30
20

8. 多态的应用

1.多态数组

com.hspedu.poly_.polyarr_ 包 PloyArray.java

数组的定义类型为父类类型,里面保存的实际元素类型为子类类型

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LH6qhMHB-1676621407165)(photo/image-20230211085719864.png)]

应用实例升级:如何调用子类特有的方法,比如

Teacher 有一个 teach , Student 有一个 study

怎么调用?

代码如下:

package com.yujianedu.ploy.polyarr_;

public class Person {//父类
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String say() {//返回名字和年龄
        return name + "\t" + age;
    }
}
package com.yujianedu.ploy.polyarr_;

public class Student extends Person {
   private double score;
   public Student(String name, int age, double score) {
       super(name, age);
       this.score = score;
   }
   public double getScore() {
       return score;
   }
   public void setScore(double score) {
       this.score = score;
   }
   //重写父类 say
   @Override
   public String say() {
       return "学生 " + super.say() + " score=" + score;
   }
   //特有的方法
   public void study() {
       System.out.println("学生 " + getName() + " 正在学 java...");
   }
}
package com.yujianedu.ploy.polyarr_;

public class Teacher extends Person{
    private double salary;
    public Teacher(String name, int age, double salary) {
        super(name, age);
        this.salary = salary;
    }
    public double getSalary() {
        return salary;
    }
    public void setSalary(double salary) {
        this.salary = salary;
    }
    //写重写父类的 say 方法
    @Override
    public String say() {
        return "老师 " + super.say() + " salary=" + salary;
    }
    //特有方法
    public void teach() {
        System.out.println("老师 " + getName() + " 正在讲 java 课程...");
    }

}
package com.yujianedu.ploy.polyarr_;

public class PloyArray {
    public static void main(String[] args) {
        //应用实例:现有一个继承结构如下:要求创建 1 个 Person 对象、
        // 2 个 Student 对象和 2 个 Teacher 对象, 统一放在数组中,并调用每个对象 say 方法
        Person[] persons = new Person[5];
        persons[0] = new Person("jack", 20);
        persons[1] = new Student("mary", 18, 100);
        persons[2] = new Student("smith", 19, 30.1);
        persons[3] = new Teacher("scott", 30, 20000);
        persons[4] = new Teacher("king", 50, 25000);

        //循环遍历多态数组,调用 say
        for (int i = 0; i < persons.length; i++) {
            //老师提示: person[i] 编译类型是 Person ,运行类型是是根据实际情况有 JVM 来判断
            System.out.println(persons[i].say());//动态绑定机制
            //这里大家聪明. 使用 类型判断 + 向下转型.
            if (persons[i] instanceof Student) {//判断 person[i] 的运行类型是不是 Student
                Student student = (Student) persons[i];//向下转型
                student.study();
                //小伙伴也可以使用一条语句 ((Student)persons[i]).study();
            } else if (persons[i] instanceof Teacher) {
                Teacher teacher = (Teacher) persons[i];
                teacher.teach();
            } else if (persons[i] instanceof Person) {
                //System.out.println("你的类型有误, 请自己检查...");
            } else {
                System.out.println("你的类型有误, 请自己检查...");
            }
        }
    }
}

执行结果:

jack	20
学生 mary	18 score=100.0
学生 mary 正在学 java...
学生 smith	19 score=30.1
学生 smith 正在学 java...
老师 scott	30 salary=20000.0
老师 scott 正在讲 java 课程...
老师 king	50 salary=25000.0
老师 king 正在讲 java 课程...

2.多态参数

方法定义的形参类型为父类类型,实参类型允许为子类类型
应用实例1:前面的主人喂动物
应用实例2:com.hspedu.poly_.polyparameter_包 PloyParameter.java

定义员工类Employee,包含姓名和月工资[private],以及计算年工资getAnnual的方法。普通员工和经理继承了员工,经理类多了奖金bonus属性和管理manage方法,普通员工类多了work方法,普通员工和经理类要求分别重写getAnnual方法

测试类中添加一个方法showEmpAnnual(Employeee),实现获取任何员工对象的年工资,并在main方法中调用该方法[e.getAnnual0]

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

代码如下:

package com.yujianedu.ploy.polyparameter_;

public class Employee {
    private String name;
    private double salary;
    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }
    //得到年工资的方法
    public double getAnnual() {
        return 12 * salary;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public double getSalary() {
        return salary;
    }
    public void setSalary(double salary) {
        this.salary = salary;
    }
}
package com.yujianedu.ploy.polyparameter_;

public class Manager extends Employee{
    private double bonus;
    public Manager(String name, double salary, double bonus) {
        super(name, salary);
        this.bonus = bonus;
    }
    public double getBonus() {
        return bonus;
    }
    public void setBonus(double bonus) {
        this.bonus = bonus;
    }
    public void manage() {
        System.out.println("经理 " + getName() + " is managing");
    }
    //重写获取年薪方法
    @Override
    public double getAnnual() {
        return super.getAnnual() + bonus;
    }
}
package com.yujianedu.ploy.polyparameter_;

public class Worker extends Employee {
    public Worker(String name, double salary) {
        super(name, salary);
    }
    public void work() {
        System.out.println("普通员工 " + getName() + " is working");
    }
    @Override
    public double getAnnual() { //因为普通员工没有其它收入,则直接调用父类方法
        return super.getAnnual();
    }
}
package com.yujianedu.ploy.polyparameter_;

public class PloyParameter {
    public static void main(String[] args) {
        Worker tom = new Worker("tom", 2500);
        Manager milan = new Manager("milan", 5000, 200000);
        PloyParameter ployParameter = new PloyParameter();
        ployParameter.showEmpAnnual(tom);
        ployParameter.showEmpAnnual(milan);

        ployParameter.testWork(tom);
        ployParameter.testWork(milan);
    }

    //showEmpAnnual(Employee e)
    //实现获取任何员工对象的年工资,并在 main 方法中调用该方法 [e.getAnnual()]
    public void showEmpAnnual(Employee e) {
        System.out.println(e.getAnnual());//动态绑定机制. }
    }

    //添加一个方法,testWork,如果是普通员工,则调用 work 方法,如果是经理,则调用 manage 方法
    public void testWork(Employee e) {
        if(e instanceof Worker) {
            ((Worker) e).work();//有向下转型操作
        } else if(e instanceof Manager) {
            ((Manager) e).manage();//有向下转型操作
        } else {
            System.out.println("不做处理...");
        }
    }
}

执行结果:

30000.0
260000.0
普通员工 tom is working
经理 milan is managing
  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值