• Java基础38 面向对象三大特征之多态




    )


    多态

    ● 多【多种】态【状态】

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

    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("表演");
    		}	
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    Cat类,继承Animal类

    class Cat extends Animal {
    		public void eat(){ //重写父类的eat
    				System.out.println("猫吃鱼");
    		}
    		public void catchMouse(){ //cat的特有方法
    				System.out.println("猫抓老鼠");
    		}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    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~");
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    3.向下转型

    多态的向下转型

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

    向下转型的使用

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

    Dog类

    public class Dog extends Animal{
    }
    
    • 1
    • 2

    在Test类中测试

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

    4.属性重写

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

    public class Test{
    	public static void main(String[] args){
    		//属性没有重写之说,属性的值看编译类型
    		Base base = new Sub(); //向上转型
    		System.out.println(base.count); //编译类型为base,则输出10
    		Sub sub = new Sub();
    		System.out.println(base.count); //编译类型为sub,则输出20
    		
    	}
    }
    
    class Base { //父类
    	int count = 10; //属性
    }
    
    class Sub extends Base { //子类
    	int count = 20; //属性
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    5.instanceOf

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

    public class Test{
    	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 = 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
    		System.out.println(AA instanceof obj);//true
    
    		String str = "yes";
    		System.out.println(str instanceof Object);//true
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    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;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    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;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在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;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在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;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    B类

    class B extends A{ //子类
    	public int i = 20;
    	public int getI(){
    		return i;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    由于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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    Student类

    public class Student extends Person{
    
        private double score;
    
        //重写父类say
        public 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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    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;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    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);
    
            //循环遍历多态数组,调用say
            for(int i = 0; i < persons.length; i++){
                //这里person[i] 编译类型是Person,运行类型是根据实际情况由JVM来判断
                System.out.println(persons[i].say());//动态绑定机制
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    运行效果

    在这里插入图片描述

    现在升级需求:如何调用子类特有的方法,比如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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    含特有方法的Student

    public class Student extends Person{
    
        private double score;
    
        //重写父类say
        public 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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    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);
    
            //循环遍历多态数组,调用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();
                    //((Teacher)persons[i]).teach();
                } else if(persons[i] instanceof Person){
    
                } else{
                    System.out.println("类型有误,请检查清楚!");
                }
            }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    运行效果

    在这里插入图片描述


    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    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;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    普通员工类(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);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    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 {
    
                }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    效果实现

    在这里插入图片描述


  • 相关阅读:
    Linux 本地 Docker Registry本地镜像仓库远程连接
    Dubbo:Nacos作为注册中心
    (C)一些题2
    特征生成(特征创建)
    elementUI+springboot实现导入文件到后端并解析excel(进阶)
    OpenShift 4 - 用 OpenShift DevSpaces 在线开发 Quarkus 云原生应用
    44_ue4进阶末日生存游戏开发[左键添加功能与丢弃功能]
    c++出现[Error] conflicting declaration ***之类的问题怎么办?
    无线传感器网络定位综述
    初步使用openEuler华为欧拉Linux系统
  • 原文地址:https://blog.csdn.net/chenjiap/article/details/127850951