面向对象编程——多态
一、由养宠物问题引出多态
我们先来看一个问题:
这个问题我们可以使用传统的方法来解决(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();
四、 多态注意事项和细节讨论
(一)多态的前提
多态的前提:两个对象(类)存在继承关系
(二)多态的向上转型
(三)多态的向下转型
(四)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的动态绑定机制:
注意:属性没有动态绑定机制!属性的值看编译类型
例如:
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:如下图要求所示
实现代码如下:
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("不做处理...");
}
}
}