面向对象编程-多态
1. 先看一个问题
请编写一个程序,Master类中有一个feed(喂食)方法,可以完成主人给动物喂食物的信息。
使用传统的方法来解决(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
- 多态的前提是:两个对象(类)存在继承关系
- 多态的向上转型
-
本质:父类的引用指向了子类的对象
-
语法:父类类型 引用名 = new 子类类型;
-
特点:
-
编译类型看左边,运行类型看右边。
-
可以调用父类中的所有成员(需遵守访问权限)
-
不能调用子类中特有成员;
- 不能调用到子类的字段
- 不能调用子类特有的方法(假如子类重写了父类的方法,在运行的时候会去调用子类的重写的方法)
-
最终运行效果看子类的具体实现!
-
-
多态向下转型
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 后老师评讲
PolyExercise02.java 3min
7. java 的动态绑定机制==(非常非常重要.)==
Java 重要特性: 动态绑定机制
Java动态绑定:在上面图中的右下角已经说明什么是动态绑定机制
下面按照顺序再来解释下:
- 当一个类B继承了类A,并且类B向上转型创建了一个对象a
- A a = new B();//向上转型
- 类A是对象a的编译类型,类B是对象a的运行类型,(a不能去调用B类特有的方法,只能调用继承了A类的方法),当对象a去调用一个方法get()时,会先去判断运行类型B中是否有该方法,假如没有该对象方法,就会去父类A中寻找是否存在get()方法
- 假如父类A中存在get()方法就会去调用该方法
- 假如在调用get()方法时,get()方法中又存在一个sum()方法,这时还是会先去运行类型B中先去寻找是否有该方法,假如有该方法就调用,假如没有,老套路,又会去父类A中寻找是否存在该方法,存在就调用返回.
- 但是假如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 方法.
应用实例升级:如何调用子类特有的方法,比如
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