多态

多态

引入

先看一个问题

问题描述 : com.edu.poly_ : Poly01.java (polymorphic: 多态的)

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

分析 :首先要有三个类 ,分别是食物类 主人类 动物类 ,其次 食物类分支有 : 鱼肉类 骨头类 米饭类 , 动物类分支 的有 猫猫类 狗狗类 猪猪类

  1. 首先用传统的思维方式解决 (private属性)

    编写代码之后我们可以发现创建一个feed方法 ,里面传入的是对象 ,如果是小狗吃骨头, 就可以把骨头类和小狗类传入

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

    代码的问题在于 ,以上的feed方法 ,如果出现了多种动物和食物 ,同时每种动物吃的食物不同 ,想要喂食所有的动物的话, 就会不断地重复编写很多遍feed方法(代码复用性不高 ,不利于代码的维护)

    提出的解决方法 : 多态

●多态基本介绍 (多态:多种状态)

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

●多态的具体体现

  1. 方法的多态 PloyMethod.java 重写和重载就体现多态

  2. 对象的多态(核心)

重要的几句话:

(1) 一个对象的编译类型和运行类型可以不一致

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

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

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

案例 : com.edu.poly.objpoly_ : PolyObject.java

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

 

●多态注意事项和细节讨论

com.edu.poly_.detail 包:PolyDetail.java

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

多态的向上转型

  1. 本质:父类的引用指向了子类的对象

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

  3. 特点:编译类型看左边,运行类型看右边。

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

不能调用子类中特有成员

最终运行效果看子类的具体实现!

//可以调用父类中的所有成员(需遵守访问权限)
//animal.catchMouse();调用这个方法就会报错
//但是不能调用子类的特有成员,例如私有方法
//因为编译阶段,能够调用哪些成员 是由编译类型决定的
//最终运行效果看子类的具体实现!
animal.eat();//调用方法的时候从子类开始查找方法,然后调用规则和前面讲的方法调用规则一致
//也就是运行类型
animal.run();
//先从子类找没有run方法,然后到父类找找到了就使用父类的
animal.show();
animal.sleep();

多态的向下转型

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

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

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

  3. 当向下转型之后 ,就可以调用子类类型中所有的成员

//希望可以调用Cat的抓猫的方法
//多态的向下转型
//此时如果是向上转型的animal的话就无法调用该方法
//子类类型 引用名   =(子类类型)父类引用;

Cat cat = (Cat) animal;
//将父类的引用强制转换为子类的引用
//此时编译类型是Cat引用类型也是Cat
cat.catchMouse();
//要求父类的引用必须指向的是当前目标类型的对象
//Dog dog = (Dog) animal;
//这样的写法是错误的,因为animal指向的堆空间为Cat对象(目标类型的对象为Cat)

●多态注意事项和细节讨论

  • 属性没有重写之说!属性的值看编译类型 PolyDetail02.java

  • instanceOf 比较操作符,用于判断对象的运行类型是否为XX类型或XX类型的子类型;[举例说明] PolyDetail03.java

package com.edu.poly_.detail;

public class PolyDetail02 {
  public static void main(String[] args) {
//属性没有重写之说!属性的值看编译类型
      Base base = new Sub();//向上转型
      System.out.println(base.count);// 这里和方法不一样, 直接看编译类型
      //编译类型为Base因此就是Base类, count打印的值就是10
      Sub sub = new Sub();
      System.out.println(sub.count);//编译类型为Sub,因此输出结果 20
  }
}

class Base { //父类
  int count = 10;//属性
}

class Sub extends Base {//子类
  int count = 20;//属性
}
package com.edu.poly_.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 {}//子类

多态的练习

package com.edu.poly_.exercise_;

public class PolyExercise01 {
  public static void main(String[] args) {
      double d = 13.4;
      long l = (long)d;
      System.out.println(l);
      int in = 5;
      //boolean b = (boolean)in;
      //Boolean类型不能转换为int
      Object obj = "Hello";
      //向上转型
      String objStr = (String)obj;
      //再把obj转换为字符串也是可以的,这属于向下转型
      System.out.println(objStr);

      Object objPri = new Integer(5);
      //可以,这是向上转型
      String str = (String)objPri;
      //错误 ,objPri指向Integer父类引用,如果强制进行转换指向String的话就会报错
      Integer str1 = (Integer)objPri;
      //向下转型将objPri转为Integer类型,因为objPri指向Integer所以是正确的

  }}
package com.edu.poly_.exercise_;

public class PolyExercise02{
  public static void main(String[] args){
      Sub s = new Sub();
      //s指向对象Sub(其中包含父类count10和子类count20)
      System.out.println(s.count);
      //访问属性看编译类型属性为s因此输出子类的count 20
      s.display();
      //执行Sub的运行类型方法输出10
      Base b = s;
      //向上转型 子类引用指向父类的引用
      System.out.println(b == s);
      //由于父类引用子类引用都指向同一块空间,因此输出true
      System.out.println(b.count);
      //b的编译类型为Base因此输出Base的count 10
      b.display();
      //方法的调用要看b的运行类型 ,b的运行空间为Sub
      //因此输出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 重要特性: 动态绑定机制 DynamicBinding.java com.edu.poly_.dynamic_

Dynamic(动态的)Binding(捆绑,绑定)

package com.edu.poly_.dynamic_;

public class DynamicBinding {
  public static void main(String[] args) {
      A a = new B();//向上转型
      System.out.println(a.sum());
      System.out.println(a.sum1());
      //执行方法看运行类型,运行类型为B()
      //因此输出的结果为40 30
  }
}
class A{//父类
  public int i = 10;
  public int sum(){
      return getI()+ 10;
  }
  public int sum1(){
      return i+10;
  }
  public int getI(){
      return i;
  }
}
class B extends A{
  public int i = 20;
  public int sum(){
      return i + 20;
  }
  public int getI(){
      return i;
  }
  public int sum1(){
      return i + 10;
  }
}

假设我们将子类的B类中的sum方法去除掉 ,再去执行主类中的 System.out.println(a.sum()); 先找运行方法即子类的B 如果找不到,就会开始执行父类A的同名方法 ,而父类的sum方法中使用了 geti方法 ,而geti方法父类和子类都有 ,因此就需要使用到动态绑定了 ,由于和运行类型进行绑定了 ,因此使用的还是运行类型的方法 ,也就是getI ,因此最终的结果就是返回20 ,输出为30

同样的去除掉B类的sum1方法去除掉 ,执行主类 的System.out.println(a.sum1()); 此时就会执行到父类的A中去 ,A的sum1方法中为i + 10 ,这个i作为属性 ,就会依照哪里声明哪里使用的原则 ,因此使用的是A类的i ,因此执行的结果就是20

java的动态绑定机制

  1. 当调用对象方法的时候,该方法会和该对象的 内存地址/运行类型 进行绑定

  2. 当调用对象属性时没有动态绑定机制,哪里声明,那里使用

     

多态的应用

  1. 多态数组com.edu.poly_.polyarr_ 包 PloyArray.java 数组的定义类型为父类类型,里面保存的实际元素类型为子类类型

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

     

代码:

package com.edu.poly_.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.edu.poly_.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.edu.poly_.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("jack",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++) {
          System.out.println(persons[i].say());
          //这里会有一个动态绑定机制
          //persons[i]编译类型是Person运行类型根据实际情况由JVM进行判断
      }
      //循环遍历多态数组 ,调用say方法
      for (int i = 0; i < persons.length; i++) {
          System.out.println(persons[i].say());
          //这里会有一个动态绑定机制
          //persons[i]编译类型是Person运行类型根据实际情况由JVM进行判断

          //由于编译类型是Person因此无法调用子类的方法
          //使用类型判断+向下转型的方式解决
          if (persons[i] instanceof Student){
              //可以通过instanceof判断person[i]运行类型是否是Student
              ((Student) persons[i]).study();
              //等同于Student student = (Student) person;
              //也就是一种向下转型
          }else if (persons[i] instanceof Teacher){
              //同样的使用instanceof进行判断
              ((Teacher) persons[i]).teach();
          }else if (persons[i] instanceof Person){
              //如果类型是Person不做任何处置
          }else {
              System.out.println("类型有误,请自己检查");
          }
      }
  }
}
输出结果 :
jack 20
学生 jack 18 score=100.0
学生 smith 19 score=30.1
老师 scott 30 salary=20000.0
老师 king 50 salary=25000.0

应用实例升级:如何调用子类特有的方法,比如 Teacher 有一个 teach , Student 有一个 study 怎么调用?

package com.edu.poly_.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("jack",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++) {
          System.out.println(persons[i].say());
          //这里会有一个动态绑定机制
          //persons[i]编译类型是Person运行类型根据实际情况由JVM进行判断

          //由于编译类型是Person因此无法调用子类的方法
          //使用类型判断+向下转型的方式解决
          if (persons[i] instanceof Student){
              //可以通过instanceof判断person[i]运行类型是否是Student
              ((Student) persons[i]).study();
              //等同于Student student = (Student) person;
              //也就是一种向下转型
          }else if (persons[i] instanceof Teacher){
              //同样的使用instanceof进行判断
              ((Teacher) persons[i]).teach();
          }else if (persons[i] instanceof Person){
              //如果类型是Person不做任何处置
          }else {
              System.out.println("类型有误,请自己检查");
          }
      }
  }
}

多态参数

方法定义的形参为父类类型 ,实参类型允许为子类类型

应用实例1:前面的主人喂食物

应用实例2:com.edu.poly_.polyparameter_包(参数) Polyparameter.java

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

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

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

package com.edu.poly_.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 salary * 12;
  }

  public String getName() {
      return name;
  }

  public double getSalary() {
      return salary;
  }

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

  public void setSalary(double salary) {
      this.salary = salary;
  }

}
package com.edu.poly_.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.edu.poly_.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.showEmAnnual(tom);
      ployParameter.showEmAnnual(milan);

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

  }
  //添加一个方法showEmAnnual(Employee e) ,实现获取任何员工对象的年工资
  public void showEmAnnual(Employee e){
      System.out.println(e.getAnnual());
  }
  //添加一个方法 ,testWork ,如果是普通的员工 ,则调用work方法 , 如果是经理 ,则调用manage方法
  public void testWork(Employee employee){
      if (employee instanceof Worker){
          ((Worker) employee).work();//向下转型的操作
      }else if (employee instanceof Manager){
          ((Manager) employee).manage();
      }else {
          System.out.println("传参的类不是员工或者经理");
      }

  }
}
package com.edu.poly_.polyparameter_;

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

  public void work(){
      System.out.println("员工:"+getName() + "正在工作....");
  }

  @Override
  public double getAnnual() {
      //因为普通员工没有其他收入,因此可以直接调用父类的方法
      return super.getAnnual();
  }
}
posted @ 2022-04-28 17:03  comia  阅读(82)  评论(0)    收藏  举报