面向对象编程——多态

面向对象编程——多态

  • 一、由养宠物问题引出多态
  • 二、多[多种]态[状态]基本介绍
  • 三、 多态的具体体现
    • (一)方法的多态
    • (二)对象的多态(核心,困难,重点)
  • 四、 多态注意事项和细节讨论
    • (一)多态的前提
    • (二)多态的向上转型
    • (三)多态的向下转型
    • (四)instanceOf 比较操作符
  • 五、 多态实践练习
    • (一)练习一
    • (二)练习二
  • 六、 Java的动态绑定机制(非常非常重要!)
  • 七、 多态的应用
    • (一)多态数组
    • (二)多态参数

一、由养宠物问题引出多态

我们先来看一个问题:
面向对象编程——多态_第1张图片

这个问题我们可以使用传统的方法来解决(private属性),但是传统的方法带来的问题是:代码的复用性不高,而且不利于代码的维护。
那么我们应该如何解决呢?解决方法就是即将要介绍的面向对象编程三大概念之一的多态。

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

这样就不利于代码的管理和维护
那么我们就可以使用多态机制:

使用多态机制,利用一个feed方法即可统一的管理主人喂食的问题
    animal 编译类型是Animal,可以指向(接收) Animal子类的对象
    food 编译类型是Food ,可以指向(接收) Food子类的对象
    
    public void feed(Animal animal, Food food) {
        System.out.println("主人 " + name + " 给 " + animal.getName() + " 吃 " + food.getName());
    }

二、多[多种]态[状态]基本介绍

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

三、 多态的具体体现

(一)方法的多态

其实重写重载就体现了我们所说的多态。

例如下面代码调用的都是sum()方法,
但是sum方法并不只有一种状态,
有计算两个数的sum方法,
也有计算三个数的sum方法,
也可以将sum方法设计成多种形式,这就是一种多态。

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;
    }
    
    public void say() {
        System.out.println("A say() 方法被调用...");
    }
}

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

四条重要性质

性质1:一个对象的编译类型和运行类型可以不一致
性质2:编译类型在定义对象时,就确定了,不能改变
性质3:运行类型是可以变化的
性质4:编译类型看定义时 = 号的左边,运行类型看 = 号的右边

例如:

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

四、 多态注意事项和细节讨论

(一)多态的前提

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

(二)多态的向上转型

面向对象编程——多态_第2张图片

(三)多态的向下转型

面向对象编程——多态_第3张图片

(四)instanceOf 比较操作符

instanceOf 比较操作符是用于判断对象的运行类型是否为XX类型或者XX类型的子类型

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 {}//子类

五、 多态实践练习

(一)练习一

判断下面的每条语句,哪些是正确的,哪些是错误的,为什么?(答案为注释部分)

public class PolyExercise01 {
    public static void main(String[] args) {
        Object obj = "Hello";//可以,向上转型(“Hello”是String类型)
        String objStr = (String)obj;//可以,向下转型
        System.out.println(objStr);//输出hello
        Object objPri = new Integer(5);//可以,向上转型
        String str = (String)objPri;//错误ClassCastException,指向Integer的父类引用,不能转成String
        Integer str1 = (Integer)objPri;//可以,向下转型
    }
}

(二)练习二

指出执行main方法每条语句会输出什么?(答案为注释部分)

public class PolyExercise02 {
    public static void main(String[] args) {
        Sub s = new Sub();
        System.out.println(s.count);//输出20
        s.display();//输出20
        Base b = s;//向上转型
        System.out.println(b == s);//输出true
        System.out.println(b.count);//输出10
        b.display();//输出20
    }

}

class Base {//父类
    int count = 10;
    public void display() {
        System.out.println(this.count);
    }
}

class Sub extends Base {//子类
    int count = 20;
    public void display() {
        System.out.println(this.count);
    }
}

六、 Java的动态绑定机制(非常非常重要!)

Java的动态绑定机制
面向对象编程——多态_第4张图片
注意:属性没有动态绑定机制!属性的值看编译类型
例如:

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;//属性
}

方法的动态绑定机制

下面代码中创建的a对象编译类型是A,运行类型是B,

当a.sun()调用方式时就遵循上述的动态绑定机制,

首先从运行类型的B类当中查找sum()方法并调用,
B中sum()方法会调用 i 成员属性,
因为属性是没有动态绑定机制的,
当前运行到B类当中,
那么调用的 i 就是B类中声明的 i ,
也就是20,
此时sum()方法返回的就是40,
同理sum1()返回的就是30.

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

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;
    }

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

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

那么如果我们把B类中的sun()和sum1()方法注释掉,下面代码运行的结果又是什么呢?
这里只有真正理解了动态绑定机制才能得出正确的答案。

当a.sun()调用方式时遵循动态绑定机制,
会首先从运行类型的B类当中查找sum()方法,
但是由于B中此时没有sun()方法,
根据继承的知识我们知道,
当子类中没有对象调用的方法时,
会从父类当中查找,
所以此时会进入到A类当中查找sum()方法并调用。
当运行到A中sum()方法里的代码

return getI() + 10; 

时,此时又会调用getI()方法,
那么刚才我们说到调用方法是要遵循动态绑定机制的,
而a对象绑定的运行类型是B,
那么此时

return getI() + 10;

代码中的getI()又会回到B中去查找getI()方法并调用,而不是调用A中的getI()方法

这里就是动态绑定机制的重要体现!

所以getI()返回的值是B中的 i 为20,程序输出30,
同理可得a.sum1()的结果是20(记住属性没有动态绑定机制),
最终程序输出的结果为30和20。

如果你能清晰的知道程序每一步运行的过程是怎么样的,并得出正确答案,那么恭喜你已经学会了动态绑定机制!

动态绑定机制在面向对象编程中是非常重要的知识点,大家务必要弄懂,如果这个代码有哪步没弄懂的,一定要回头重新理解!

public class DynamicBinding {
    public static void main(String[] args) {
        //a 的编译类型 A, 运行类型 B
        A a = new B();//向上转型
        System.out.println(a.sum());//输出30
        System.out.println(a.sum1());//输出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;
    //}

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

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

七、 多态的应用

(一)多态数组

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

应用实例:
现有一个继承结构如下:
要求创建 1 个 Person 对象、2 个 Student 对象和 2 个 Teacher 对象,
统一放在数组中,并调用每个对象say()方法、调用子类特有的方法。
(比如 Teacher 有一个 teach()方法 , Student 有一个 study()方法)

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;
    }
}

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...");
    }
}

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课程...");
    }
}


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("你的类型有误, 请自己检查...");
            }

        }

    }
}

(二)多态参数

应用实例1:前面文章所提到的主人喂动物问题

应用实例2:如下图要求所示
面向对象编程——多态_第5张图片
实现代码如下:

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;
    }
}

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;
    }
}

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();
    }
}

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("不做处理...");
        }
    }
}

你可能感兴趣的:(Java基础,java)