• 一文带你快速掌握面向对象三大特性之【多态】


    1. 多态的概念

    通俗来讲,就是多种形态。就是完成某一个行为时,如果不同对象去完成就会产生不同的状态。
    总的来讲,同一件事情,发生在不同对象的身上,就会产生不同的结果。

    2. 多态的实现条件

    • 1、必须在继承类下 (继承)
    • 2、子类必须要对父类中的方法进行重写 (重写)
    • 3、 通过父类的引用调用重写方法 (向上转型)

    下面们,我们来详细解析下讲下这三个条件

    2.1 必须在继承类下

    这里我们创建 一个父类 Animal 和 两个子类 Dog 和 Cat

    class Animal{
        public String name;
        public int age;
        public Animal(String name,int age){  // 带有两个参数的构造方法
            this.name = name;
            this.age = age;
        }
        public void eat(){
            System.out.println("吃饭");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    class Dog extends Animal{
        public Dog(String name ,int age){
            super(name,age);
        }
    
        @Override
        public void eat() {
            System.out.println("吃狗粮");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    class Cat extends Animal{
    
        public Cat(String name ,int age){
            super(name,age);
        }
    
        @Override
        public void eat() {
            System.out.println("吃猫粮");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2.2 子类必须对父类中的方法进行重写

    Dog类重写 eat() 方法
    在这里插入图片描述

    Cat类重写 eat() 方法
    在这里插入图片描述


    2.3 通过父类的引用调用重写方法

    在测试类中,我们通过父类的引用了重写的方法

    public class Test {
        public static void func(Animal animal){
            animal.eat();
        }
        public static void main(String[] args) {
            Animal animal1 = new Dog("大黄",1);
            func(animal1);
    
            Animal animal2 = new Cat("喵喵",2);
            animal2.eat();       // 通过父类的引用 调用重写方法
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    最终的输出结果就是:
    在这里插入图片描述

    2.4 总结一下

    我们来画个图分析下, 下面这个例子就形成了多态的条件
    在这里插入图片描述


    那么,程序是如何运行的呢?我们继续来看一个图,来分析下
    在这里插入图片描述


    在将 class文件,进行反汇编以后,也可以看出 程序在编译的时候,也是调用的 父类的 eat() 方法
    在这里插入图片描述
    而在运行的时候,却是调用了重写以后的方法,这就是动态绑定,也是发生多态的基础!

    • 动态绑定:也称后期绑定(晚绑定),在编译时,不能确定方法的行为,需要等到程序运行时,才能够确认具体调用哪个类的方法
    • 静态绑定:也称前期绑定(早绑定),在编译时,根据用户所传递的实参类型就确定了具体调用哪个方法,例如:函数重载

    3. 什么是重写

    重写(override):也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写。 有以下几条规则

    • private 修饰的方法不可以被修饰
    • static 修饰的方法不能被重写
    • 子类的访问权限 要 大于等于 父类的权限
    • final 关键字 修饰的方法 不能被重写,(此时这个方法叫密封方法)

    既然讲到重写,那么我们就来对比下重写重载的区别

    在这里插入图片描述

    方法的重载是一个类的多态的表现
    方法重写是子类和父类的一种多态表现

    4. 向上转型和向下转型

    4.1 向上转型

    创建一个子类对象,将其当成父类对象来使用
    语法格式: 父类类型 对象名 = new 子类类型()

    Animal animal = new Cat();
    
    • 1

    使用场景有三种:

    • 直接赋值
    Animal animal = new Cat("旺财",5);
    
    • 1

    • 方法传参
    public class Test {
        public static void func(Animal animal){
            animal.eat();
        }
        public static void main(String[] args) {
            Animal animal1 = new Dog("大黄",1);
            Animal animal2 = new Cat("喵喵",2);
    
            func(animal1);
            func(animal2);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    • 方法的返回
        public static Animal funciton(String name){
            return new Dog("大黄",2);
        }
    
    • 1
    • 2
    • 3

    向上转型的优点:让代码更加灵活,可以引用更多的类型
    向上转型的缺点:不能调用子类特有的方法(除非重写,发生动态绑定)

    4.2 向下转型

    语法格式
    父类类型 父类对象名 = new 子类类型()
    子类类型 子类对象名 = (子类类型)父类对象名

            Animal animal1 = new Dog("黑咯破",1);
            Dog dog = (Dog) animal1;
    
    • 1
    • 2

    向下转型用的比较少,不安全,可以从下图中看出不安全。

    在这里插入图片描述


    Java 为了提高向下转型的安全性,引入 instance关键字,如果该表达式 为 true,则可以安全转换。
    在这里插入图片描述

    5.1 使用多态的好处和缺陷

    好处:

    • 能够降低代码的 圈复杂度,避免使用大量的 if-else
    • 可扩展能力更强

    多态也有缺陷:

    • 属性没有多态
    • 构造方法没有多态

    我们要避免在构造方法中调用重写的方法

    class B {
    	public B() {
    		// do nothing
    		func();
    	}
    	public void func() {
    		System.out.println("B.func()");
    	}
    }
    class D extends B {
    	private int num = 1;
    	@Override
    	public void func() {
    		System.out.println("D.func() " + num);
    	}
    }
    public class Test {
    	public static void main(String[] args) {
    		D d = new D();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    上述代码执行的结果是 D.fun() 0 , 通过下图来看执行步骤

    在这里插入图片描述

    当在父类的构造方法中 去调用父类和子类的重写方法时,此时会调用子类的!

  • 相关阅读:
    CSS读书笔记
    uniapp vue3 使用pinia存储 并获取数据
    数据类型与变量
    【nvm】
    Linux之多线程
    聚观早报 | 茅台市值超过腾讯位列第一;三星正研发智能戒指
    ZedGraph如何去掉外边框?并设置背景颜色
    谷粒学院——Day02【环境搭建和讲师管理接口开发】
    数据库备份与恢复
    【转载】分布式训练和集合通信
  • 原文地址:https://blog.csdn.net/hero_jy/article/details/127833766