Java基础38 面向对象三大特征之多态
创始人
2024-02-01 11:35:09

OOP之多态

  • 多态
    • 1.多态的具体体现
    • 2.向上转型
    • 3.向下转型
    • 4.属性重写
    • 5.instanceOf
    • 6.动态绑定机制(核心)
      • ● Java的动态访问机制
    • 7.多态数组
    • 8.多态参数

)


多态

● 多【多种】态【状态】

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

1.多态的具体体现

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

  2. 对象的多态(核心)
    (1)一个对象的编译类型和运行类型可以不一致。
    (2)编译类型在定义对象时,就确定了,不能改变。
    (3)运行类型是可以变化的。
    (4)编译类型看定义时 “=” 号 的左边,运行看 “=” 号的右边

例如:
Animal animal = new Dog();
【这里animal编译类型是Animal,运行类型是Dog】
animal = new Cat();
【animal的运行类型变成了Cat,编译类型仍然是Animal】

2.向上转型

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

多态的向上转型

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

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

  3. 特点:

    • 编译类型看左边,运行类型看右边。
    • 可以调用父类中的所有成员(需遵守访问权限),不能调用子类中特有成员;
    • 最终运行效果看子类的具体实现。

向上转型调用方法的规则
(1)可以调用父类中的所有成员(需遵守访问权限)。
(2)但是不能调用子类的特有成员。
(3)因为在编译阶段,能调用哪些成员,是由编译类型来决定的,最终运行效果看子类(运行类型)的具体实现,即调用方法时,按照从子类(运行类型)开始查找方法,然后调用,规则与方法调用规则一致。

以下面例子为例说明向上转型的规则

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("表演");}	
}

Cat类,继承Animal类

class Cat extends Animal {public void eat(){ //重写父类的eatSystem.out.println("猫吃鱼");}public void catchMouse(){ //cat的特有方法System.out.println("猫抓老鼠");}
}

Test类

public class Test{public static void mian(String[] args){//向上转型:父类的引用指向子类的对象//语法:父类类型引用名 = new 子类类型();Aniaml animal = new Cat();// Object obj = new Cat(); 也是可以的,因为Object也是Cat的父类//调用父类中的所有成员(需遵守访问权限)//不能调用子类的特有成员//animal.catchMouse(); 是错误的,因为catchMouse是Cat中的特有方法animal.eat(); animal.run(); animal.show();animal.sleep();System.out.println("out~");}
}

3.向下转型

多态的向下转型

  1. 语法:子类类型 引用名 = (子类类型) 父类引用
  2. 只能强转父类的引用,不能强转父类的对象
  3. 要求父类的引用必须指向的是当前目标类型的对象
  4. 可以调用子类类型中所有的成员

向下转型的使用

以Aniaml类与cat类为例,新增一个dog类

Dog类

public class Dog extends Animal{
}

在Test类中测试

调用Cat的catchMouse方法
//使用多态的向下转型
//(1)语法 : 子类类型 引用名 = (子类类型) 父类引用;
Cat cat = (Cat) animal; //cat的编译类型是Cat,运行类型是Cat
cat.catchMouse();
//(2)父类的引用必须指向的是当前目标类型的对象
//Dog dog = (Dog)animal; //这句会报错,提示类异常

4.属性重写

属性没有重写之说,属性的值看编译类型。

public class Test{public static void main(String[] args){//属性没有重写之说,属性的值看编译类型Base base = new Sub(); //向上转型System.out.println(base.count); //编译类型为base,则输出10Sub sub = new Sub();System.out.println(base.count); //编译类型为sub,则输出20}
}class Base { //父类int count = 10; //属性
}class Sub extends Base { //子类int count = 20; //属性
}

5.instanceOf

nstanceOf 比较操作符,用于判断对象的运行类型是否为XX类型或XX类型的子类型。

public class Test{public static void main(String[] args){BB bb = new BB();System.out.println(bb instanceof BB);//trueSystem.out.println(bb instanceof AA);//trueAA aa = new BB();System.out.println(aa instanceof AA); //trueSystem.out.println(aa instanceof BB); //trueObject obj = new Object();System.out.println(obj instanceof AA);//falseSystem.out.println(AA instanceof obj);//trueString str = "yes";System.out.println(str instanceof Object);//true}
}

6.动态绑定机制(核心)

Java重要特征:动态绑定机制

现在有两个类,A类与B类,其中B类是A类的子类,在main方法中创建对象: A a = new B();调用a.sum()与a.sum1()看看分别得到什么结果?

A类

class A{ //父类public int i =10;public int sum(){return getI() + 10;}public int sum1(){return i + 10;}public int getI(){return i;}
}

B类

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;}
}

在main方法中 A a = new B(); //向上转型 System.out.println(a.sum()); //输出40
System.out.println(a.sum1()); //输出30

现在注释掉B类中的sum方法,那输出的结果又该如何?

B类

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;}
}

在main中调用
System.out.println(a.sum());

因为B类中的sum被注释了,所以会自动向上找sum方法,而父类A中恰好有sum方法,所以调用了A类的sum方法,而A类的sun方法里返回的是getI()+10;而A与B类中都有getI方法,这里就涉及到了java的动态访问机制

● Java的动态访问机制

  1. 当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定。
  2. 当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用。【属性没有动态绑定机制】

现在回过头去看刚才的问题,刚才调用的getI()方法,是属于对象的方法,所以该方法会和该对象的内存地址/运行类型绑定,我们创建的时候使用的运行类型是B类型,所以getI应该调用B类的getI方法:return
i;所以最后a.sum()输出的结果为:20+10 = 30。

那如果再把B类中的sum1()方法再注删掉呢?a.sum1()的结果是什么?

A类

class A{ //父类public int i =10;public int sum(){return getI() + 10;}public int sum1(){return i + 10;}public int getI(){return i;}
}

B类

class B extends A{ //子类public int i = 20;public int getI(){return i;}
}

由于B中并没有sum1方法,所以依然调用父类A的sum1方法,而sum1()中的 i + 10;i为属性,前面介绍过,java动态绑定机制是:当调用对象的属性时,,没有绑定动态机制,就是哪里声明,哪里使用,所以直接返回B类中的i,结果为:10 + 10 = 20


7.多态数组

多态数组

数组的定义类型为父类类型,里面保存的实际元素类型为子类类型。

应用实例:

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

代码实现

Person类

public class Person { //父类private String name;private int age;public String say(){ //返回名字和年龄return name + "\t" + 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;}
}

Student类

public class Student extends Person{private double score;//重写父类saypublic String say(){return "学生" + super.say() + "score =" + 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;}
}

Teacher类

public class Teacher extends Person{private double salary;//重写父类的say方法public String say(){return "老师 :" + super.say()+"salary = " + 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;}
}

Test类(主方法类)

创建多态数组并遍历调用say方法

public class test {
//    在main中,分别创建Person和Student对象,调用say方法输出自我介绍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("queen",29,25000);//循环遍历多态数组,调用sayfor(int i = 0; i < persons.length; i++){//这里person[i] 编译类型是Person,运行类型是根据实际情况由JVM来判断System.out.println(persons[i].say());//动态绑定机制}}
}

运行效果

在这里插入图片描述

现在升级需求:如何调用子类特有的方法,比如Teacher有一个teach,Student有一个study怎么调用?

含特有方法的Teacher

public class Teacher extends Person{private double salary;//重写父类的say方法public String say(){return "老师 :" + super.say()+"salary = " + salary;}//teacher特有方法public void teach(){System.out.println("老师:" +getName() + "正在授课");}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;}
}

含特有方法的Student

public class Student extends Person{private double score;//重写父类saypublic String say(){return "学生" + super.say() + "score =" + score;}public void study(){System.out.println("学生:" + getName() + "正在上课");}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;}
}

Test类

通过向下转型与类型判断完成子类特有方法的调用。

public class test {
//    在main中,分别创建Person和Student对象,调用say方法输出自我介绍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("queen",29,25000);//循环遍历多态数组,调用sayfor(int i = 0; i < persons.length; i++){//这里person[i] 编译类型是Person,运行类型是根据实际情况由JVM来判断System.out.println(persons[i].say());//动态绑定机制//使用 类型判断 + 向下转型if(persons[i] instanceof Student){ //判断person[i] 的运行类型是不是StudentStudent student = (Student) persons[i];student.study();//熟悉后可以简化为一条语句://((Student)persons[i]).study();} else if(persons[i] instanceof Teacher){Teacher teacher = (Teacher) persons[i];teacher.teach();//((Teacher)persons[i]).teach();} else if(persons[i] instanceof Person){} else{System.out.println("类型有误,请检查清楚!");}}}}

运行效果

在这里插入图片描述


8.多态参数

多态参数:方法定义的形参类型为父类类型,实参类型允许为子类类型。

应用案例

  • 定义员工类Employee,包含姓名和月工资【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 12 * salary;}public Employee(String name, double salary) {this.name = name;this.salary = salary;}public void setName(String name){this.name = name;}public String getName(){return name;}public double getSalary() {return salary;}public void setSalary(double salary) {this.salary = salary;}
}

manage类

public class manger extends Employee{private double bonus;//获取年薪public double getAnnual(){return super.getAnnual() + bonus;}public void manage(){System.out.println("经理: "+ getName()+"is working");}public manger(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;}
}

普通员工类(Ordinary_employees)

public class Ordinary_employees extends Employee{public void work(){System.out.println("普通员工 " +getName() +"is working");}public double getAnnual(){ //因为普通员工没有其他收入,则直接调用父类方法就可以return super.getAnnual();}public Ordinary_employees(String name,double salary){super(name,salary);}
}

test类

创建普通员工与经理的对象,添加两个新方法并输出需求。

public class test {//    在main中,分别创建Person和Student对象,调用say方法输出自我介绍public static void main(String[] args) {Ordinary_employees tom = new Ordinary_employees("tom",2500);manger milan = new manger("milan",5000,250000);test t = new test();t.showEmpAnnual(tom);t.showEmpAnnual(milan);t.testWork(tom);t.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 Ordinary_employees){((Ordinary_employees)e).work();//向下转型}else if(e instanceof  manger){((manger)e).manage(); //向下转型} else {}}

效果实现

在这里插入图片描述


相关内容

热门资讯

北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...