目录
一、多态
多态:一个事物的多种形态;一个对象具有多种形态。多态是面向对象的第三大特征,多态是建立在封装和继承基础之上的。
1. 多态的具体体现
1.1 方法的多态
- 方法重写和方法重载体现了方法的多态。
- 代码举例:
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 {
// 两个sum 方法构成方法重载
public int sum(int n1, int n2){// 和下面sum 构成重载
return n1 + n2;
}
public int sum(int n1, int n2, int n3){
return n1 + n2 + n3;
}
// 重写了父类中的say 方法
public void say() {
System.out.println("A say() 方法被调用...");
}
}
1.2 对象的多态(核心、重难点)
- 重要概念:(要背)
- 说明:一个对象在创建时,有两种类型,一种是编译类型,另一种是运行类型;语法如下:
- 编译类型 对象名 = new 运行类型();
- 举例说明:
- 注意:若编译类型为父类,则对象名又称为 父类的引用,下面会使用后者以区分普通的创建创建对象。
- 代码说明:
//体验对象多态特点
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(); //小猫喵喵叫
}
}
class Animal {
public void cry() {
System.out.println("Animal cry() 动物在叫....");
}
}
class Dog extends Animal {
public void cry() {
System.out.println("Dog cry() 小狗汪汪叫...");
}
}
class Cat extends Animal {
public void cry() {
System.out.println("Cat cry() 小猫喵喵叫...");
}
}
2. 多态的注意事项和细节
- 实现多态的前提是:两个对象(类)存在继承关系。
2.1 多态的向上转型
- 基本概念:
- 解释:以 Animal a = new Cat(); 为例
- 首先由于Animal、Cat 类拥有父子类的继承关系,在方法区中分别加载了两者的类信息;
- 其次,在堆内存中,仅开辟了一个 Cat类的对象空间,并没有开辟Animal 类的对象空间;
- 接着,在对象空间中进行初始化属性,父子类的属性都会初始化;
- 然后,将初始化完毕的对象空间地址返回给父类的引用;
- 注意:
- 实质上父类的引用是指向了子类对象,由于继承关系,所以父类的引用可以调用父类中的所有成员(调用时需遵守访问权限);
- 由于编译类型不是子类本身,所以父类的引用不能调用子类中的特有成员(换句话说,父类没有而子类有的成员,不能调用;父类有而子类没有的成员,可以调用);
- 由于继承关系,在调用父子类中同名的方法时,根据运行类型来决定调用顺序(换句话说,运行类型是子类类型,所以优先调用子类的同名方法,任何情况都成立)。
2.2 多态的向下转型
- 基本概念:
- 向上转型后有个不足之处,那就是父类的引用不能访问子类中特有的成员,怎么解决?即使用向下转型。
- 解释:以 Animal a = new Cat(); 为例
- 向下转型语法:Cat cat = (Cat) a ;
- 父类的引用必须指向的是当前目标类型的对象,意思是说,在定义时父类的引用是指向Cat 类对象的,那么向下转型时父类的引用也只能转成Cat 类对象,而不能转成其他类的对象;
- 向下转型后,cat 可以调用其指向的对象空间的所有成员(注意访问权限),相当于 Cat cat = new Cat();
- 代码解释:
public class PolyDetail {
public static void main(String[] args) {
//向上转型: 父类的引用指向了子类的对象
//语法:父类类型引用名 = new 子类类型();
Animal animal = new Cat();
Object obj = new Cat();//可以吗? 可以,因为 Object 也是 Cat的父类
//向上转型调用方法的规则如下:
//(1)可以调用父类中的所有成员(需遵守访问权限)
//(2)但是不能调用子类的特有的成员
//(#)因为在编译阶段,能调用哪些成员,是由编译类型来决定的
animal.catchMouse(); // 错误,子类特有的方法,不能调用
//(4)最终运行效果看子类(运行类型)的具体实现,
// 即调用方法时,按照从子类(运行类型)开始查找方法,然后调用
animal.eat();//猫吃鱼..调用了子类的eat方法;
animal.run();//跑
animal.show();//hello,你好
animal.sleep();//睡
//若想要调用Cat的 catchMouse方法
//多态的向下转型
//(1)语法:子类类型 引用名 =(子类类型)父类引用;
Cat cat = (Cat) animal;
cat.catchMouse();//猫抓老鼠,向下转型后可以输出
//(2)要求父类的引用必须指向的是当前目标类型的对象
Dog dog = (Dog) animal; //可以吗?不可以
}
}
// 动物类
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,你好");
}
}
// 猫类继承动物类
class Cat extends Animal {
public void eat(){//方法重写
System.out.println("猫吃鱼");
}
public void catchMouse(){//Cat特有方法
System.out.println("猫抓老鼠");
}
}
// 狗类继承动物类
class Dog extends Animal {
//Dog是Animal的子类
}
2.3 多态中属性的调用
- 属性和方法不同,没有重写之说,因此调用属性由编译类型决定。
- 与继承的属性调用比较:继承中的编译类型和运行类型都是子类类型,所以直接调用属性时,是从子类开始查找的。
- 代码解释:
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.4 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);// true
System.out.println(aa instanceof BB);// true
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 {}//子类
3. Java 的动态绑定机制(非常非常重要!)
- 当调用对象方法的时候,该方法会和对象的内存地址/运行类型绑定;
- 当调用对象属性时,没有动态绑定机制,处在哪个类中,便调用该类的属性;
- 举例说明:
- 思考:将上图的B 类中的sum 方法和sum1 方法注释掉后,main 方法中的输出语句结果是什么?
- 代码说明:
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;
// }
}
- 解释:
- a.sum 语句: 子类B 中没有该方法,则调用父类A 的sum 方法,在sum 方法中又调用了getI 方法,根据动态绑定机制,调用对象方法时,该方法会和对象的运行类型绑定,因此,此时调用的是子类B 中的getI方法;在子类B 中的gatI 方法中,调用了属性 i,调用对象属性时,处在哪个类中,便调用该类的属性,因此此时调用的是子类B 中的属性 i;所以最后a.sum 返回的结果是 30。
- a.sum1 语句: 子类B 中没有该方法,则调用父类A 的sum1 方法,在子类A 中的sum1 方法中,调用了属性 i,调用对象属性时,处在哪个类中,便调用该类的属性,因此此时调用的是子类A 中的属性 i;所以最后a.sum1 返回的结果是 20。
- 注意!对于对象属性的调用,是没有动态绑定机制的;对象属性的直接调用是由编译类型决定。但是在上例中,是在对象方法中调用了属性,因此调用时处在哪个类中,便调用该类的该属性。(这是我个人的理解)
4. 多态的应用
4.1 多态数组
- 多态数组:数组的定义类型为父类类型,里面保存的实际元素类型为子类类型。
- 应用实例:现有一个继承结构如下:要求创建 1 个 Person 对象、2 个 Student 对象和 2 个 Teacher 对象, 统一放在数组 中,并调用每个对象 say 方法。还需调用子类特有的方法,比如 Teacher 有一个 teach , Student 有一个 study 怎么调用?
- 代码实现:
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("你的类型有误, 请自己检查...");
}
}
}
}
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;
}
}
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课程...");
}
}
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...");
}
}
4.2 多态参数
- 多态参数:方法定义的形参类型为父类类型,传入的实参类型允许为子类类型。
- 应用实例:
- 代码实现:
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, 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) {
for (int i = 0; i < e.length; i++) {
if (e[i] instanceof Worker) {
((Worker) e[i]).work();
} else if (e[i] instanceof Manager) {
((Manager)e[i]).manage();
}
}
}
}
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;
}
}
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;
}
}
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();
}
}