目录
一、多态的引入
编写一个程序,实现主人喂食:有个主人类,给不同的动物喂食不同的食物。我们前面学完了封装和继承,利用之前的知识体系,要实现这个需求,可以这么做:
创建不同的动物类和食物类,所有的动物可以继承父类Animal,所有的食物类可以继承父类Food。每次喂食的方式与对象不同,要写很多喂食方法,如下图。可见,图中的多个feed方法实现了方法的重载,代码看起来冗余,复用性不高,难以维护和管理。
那么,能不能只用写一个feed方法,就可以实现不同的喂食效果呢?即给不同的动物喂食不同的食物。因此,我们就需要多态来解决这个问题。
继承体系:
二、多态的基本介绍
多态是指方法或对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承基础之上的。
(一)方法的多态——重载和重写
public class PloyMethod {
public static void main(String[] args) {
//方法重载体现多态
A a = new A();
//这里我们传入不同的参数,就会调用不同sum方法,就体现多态
System.out.println(a.sum(10, 20)); // 30
System.out.println(a.sum(10, 20, 30)); // 60
//方法重写体现多态
B b = new B();
a.say(); // A say() 方法被调用...
b.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() 方法被调用...");
}
}
(二)对象的多态(多态的核心)
- 一个对象的编译类型和运行类型可以不一致。即父类的引用指向子类的对象。例如:Animal animal = new Dog(); [animal 编译类型是Animal,运行类型是Dog,Dog类继承了Animal类]。
- 编译类型在定义对象时,就确定了,不能改变。
- 运行类型是可以变化的。例如上面的animal可以指向Cat类:animal = new Cat(); [animal的运行类型变成了Cat,编译类型仍然是Animal]
- 编译看(=号)左边,运行看(=号)右边。
代码示例:
public class Animal { // 父类
public void cry() {
System.out.println("Animal cry() 动物在叫....");
}
}
public class Cat extends Animal { // 子类
@Override
public void cry() {
System.out.println("Cat cry() 小猫喵喵叫...");
}
}
public class Dog extends Animal { // 子类
@Override
public void cry() {
System.out.println("Dog cry() 小狗汪汪叫...");
}
}
public class PolyObject {
public static void main(String[] args) {
// 对象的多态
// 编译类型:Animal 运行类型:Dog
Animal animal = new Dog();
// 因为运行到这里时,animal的运行类型是Dog,所以cry方法时Dog的cry
animal.cry(); // Dog cry() 小狗汪汪叫...
// 编译类型:Animal 运行类型:Cat
animal = new Cat();
animal.cry(); // Cat cry() 小猫喵喵叫...
}
}
三、多态的快速入门
现在我们可以使用多态机制,可以统一管理主人喂食的问题:只需要改写Master类中的feed方法即可。
原有main方法中的代码不必更改,实现了和原来一样的效果,再创建更多的动物和食物也可以直接调用feed方法。
四、向上转型
多态的使用前提:两个对象(类)存在继承关系。
多态的向上转型:
本质:父类的引用指向子类的对象
语法:父类类型 引用名 = new 子类类型;
特点:编译看左边,运行看右边。
可以调用父类中的所有成员(需遵守访问权限),不能调用子类中特有成员,最终运行效果看子类的具体实现!
向上转型调用方法的规则如下:
(1)可以调用父类中的所有成员(需遵守访问权限)
(2)但是不能调用子类的特有的成员
(3)在编译阶段,能调用哪些成员,是由编译类型来决定的
(4)最终运行效果看子类(运行类型)的具体实现,即调用方法时,按照从子类(运行类型)开始查找方法,然后调用,规则与继承部分方法调用规则一致。
代码示例:
编写父类Animal:
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,你好"); } }
编写子类Cat,继承父类Animal:
public class Cat extends Animal { @Override public void eat() { // 方法重写 System.out.println("猫吃鱼"); } public void catchMouse() { // Cat特有方法 System.out.println("猫抓老鼠"); } }
main方法实现多态的向上转型:
public class PolyDetail { public static void main(String[] args) { //向上转型: 父类的引用指向了子类的对象 //语法:父类类型引用名 = new 子类类型(); Animal animal = new Cat(); Object obj = new Cat();//这样写是可以的,因为 Object 也是 Cat的父类 // animal.catchMouse();错误 不能调用子类特有成员 // 编译时父类有成员,且有权限,可以调用 // 运行时的效果看子类,子类如果没有成员,再看父类,调用父类的成员 animal.eat();//猫吃鱼.. animal.run();//跑 animal.show();//hello,你好 animal.sleep();//睡 } }
五、向下转型
我们知道,向上转型只能调用父类中的方法,但是如果想要调用子类中的方法,就需要向下转型。
多态的向下转型:
1)语法:子类类型 引用名 = (子类类型)父类引用;
2)只能强转父类的引用,不能强转父类的对象
3)要求父类的引用,必须指向的是当前目标类型的对象
4)可以调用子类类型中所有的成员
还是上面的代码,如果想要调用Cat的 catchMouse方法,实现方法如下:
PolyDetail类:
public class PolyDetail {
public static void main(String[] args) {
//多态的向下转型
//(1)语法:子类类型 引用名 =(子类类型)父类引用;
// cat 的编译类型 Cat,运行类型是 Cat
Animal animal = new Cat();
Cat cat = (Cat) animal;
cat.catchMouse(); // 猫抓老鼠
//(2)要求父类的引用,必须指向的是当前目标类型的对象
// 下面这样写运行会报ClassCastException,因为animal原来是指向Cat的
Dog dog = (Dog) animal;
// animal原本是指向Cat的,现在向下转型指向了Dog是不合理的,因为Cat与Dog之间没有关系
}
}
六、属性不存在重写,只看编译类型
(一)案例1
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;// 属性
}
(二)案例2
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);//T
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);
}
}
七、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 {
}// 子类
八、Java的动态绑定机制(重要)
class A {//父类
public int i = 10;
//动态绑定机制:
//父类sum()
public int sum() {return getI() + 10;}
//父类sum1()
public int sum1() {return i + 10;}
//父类getI
public int getI() {return i;}
}
class B extends A {//子类
public int i = 20;
public int sum() {return i + 20;}
//子类getI()
public int getI() {return i;}
public int sum1() {return i + 10;}
}
public class DynamicBinding {
public static void main(String[] args) {
//a 的编译类型 A, 运行类型 B
A a = new B();//向上转型
System.out.println(a.sum());
System.out.println(a.sum1());
}
}
调用a.sum()时,实际调用的是B类中的sum方法,所以输出40;
调用a.sum1()时,实际调用的也是B类中的sum1方法,所以输出30;
当我们把B类中的sum()和sum1()方法注释掉以后,结果是什么?
思路解析:
刚开始调用a.sum(),实际仍然是去B类中查找sum()方法,发现B类中没有sum()方法,所以就去B类的父类,也就是A类中查找;
在A类中找到了sum()方法,但是return getI() + 10;里面又调用一个getI()方法,注意!此时就会再次回到B类中查找是否有getI()方法!在B类中找到getI(),返回的是B类中的属性i的值,为20,所以getI()=20;
再次回到return getI() + 10;这句代码,已知getI()=20,那么a.sum()返回值就是20+10=30;
再来看a.sum1()方法,它与上面的一样:先去B类中查找是否有sum1()方法,没有找到,又去A类中查找sum1()方法,在A类中找到了,进入方法,return i + 10; 这里的i是A类的i属性的值,也就是10;所以,return i + 10=10+10=20,所以a.sum1()=20。
class A {//父类
public int i = 10;
//动态绑定机制:
//父类sum()
public int sum() {return getI() + 10;}
//父类sum1()
public int sum1() {return i + 10;}
//父类getI
public int getI() {return i;}
}
class B extends A {//子类
public int i = 20;
// public int sum() {return i + 20;}
//子类getI()
public int getI() {return i;}
// public int sum1() {return i + 10;}
}
九、多态的应用
(一)多态数组
数组的定义类型为父类类型,里面保存的实际元素类型为子类类型
应用实例:现有一个继承结构如下:要求创建1个Person对象、2个Student对象和2个Teacher对象, 统一放在数组中,并调用每个对象say方法。
Person类:
Student类:
Teacher类:
遍历Persons数组,调用say方法:
public class PloyArray {
public static void main(String[] args) {
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
// person[i] 编译类型是 Person ,运行类型是是根据实际情况有JVM来判断
for (int i = 0; i < persons.length; i++) {
System.out.println(persons[i].say());
}
}
}
运行结果:
应用实例升级:如何调用子类特有的方法,比如Teacher有一个teach,Student有一个study,如何调用?
给Teacher类添加teach方法:
public void teach() {
System.out.println("老师 " + getName() + " 正在讲java课程...");
}
个体Student类添加study方法:
public void study() {
System.out.println("学生 " + getName() + " 正在学习java课程...");
}
修改main方法中的内容:
public class PloyArray {
public static void main(String[] args) {
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++) {
System.out.println(persons[i].say());
// persons[i].teach(); 错误,无法调用
// 使用类型判断+向下转型 就能调用子类特有的方法
if (persons[i] instanceof Student) {
// Student student = (Student) persons[i];
// student.study();
// 或者
((Student) persons[i]).study();
} else if (persons[i] instanceof Teacher) {
((Teacher) persons[i]).teach();
} else if (persons[i] instanceof Person) {
// 不做任何处理
} else { // 三种类型都不是再输出
System.out.println("你的类型有误,请检查...");
}
}
}
}
运行结果:
(二)多态参数
方法定义的形参类型为父类类型,实参类型允许为子类类型
应用实例1:文章开篇的主人喂食动物
应用实例2:
定义员工类Empolyee,包含姓名和月工资[private],以及计算年工资getAnnual的方法。普通员工和经理继承了员工,经理类多了奖金bonus属性和管理manage方法,普通员工类多了work方法,普通员工和经理类要求分别重写getAnnual方法。
测试类中添加一个方法showEmpAnnual(Employee e),实现获取任何员工对象的年工资,并在main方法中调用该方法[e.getAnnual()]。
测试了中添加一个方法testWork,如果是普通员工,则调用work方法;如果是经理,则调用manage方法。
代码实现:
Employee类:
public class Employee {
private String name;
private double salary;
// 计算年工资
public double getAnnual() {
return salary * 12;
}
public Employee(String name, double salary) {
this.name = name;
this.salary = 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;
}
}
普通员工Worker类:
public class Worker extends Employee {
public Worker(String name, double salary) {
super(name, salary);
}
@Override
public double getAnnual() {
return super.getAnnual();
}
public void work() {
System.out.println("普通员工" + getName() + "在工作...");
}
}
经理Manager类:
public class Manager extends Employee {
private double bonus;
public Manager(String name, double salary) {
super(name, salary);
}
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;
}
@Override
public double getAnnual() {
return super.getAnnual() + bonus;
}
public void manager() {
System.out.println("经理" + getName() + "在管理员工...");
}
}
测试类PolyParameter:
public class PolyParameter {
public static void main(String[] args) {
Worker zhangsan = new Worker("zhangsan", 5000);
Manager lisi = new Manager("lisi", 15000, 6000);
PolyParameter polyParameter = new PolyParameter();
polyParameter.showEmpAnnual(zhangsan); // 60000.0
polyParameter.showEmpAnnual(lisi); // 186000.0
polyParameter.testWork(zhangsan); // 普通员工zhangsan在工作...
polyParameter.testWork(lisi); // 经理lisi在管理员工...
}
public void showEmpAnnual(Employee e) {
// 这里有动态绑定机制,不需要用instanceof判断
System.out.println(e.getAnnual());
}
public void testWork(Employee e) {
if (e instanceof Worker) {
// 调用子类特有方法,需要向下转型
((Worker) e).work();
} else if (e instanceof Manager) {
// 调用子类特有方法,需要向下转型
((Manager) e).manager();
}
}
}