• 多态 polymorphism


    多态 polymorphism


    前言 : 本 主要内容: 多态

    多态: 字面 意义 多种 形态 / 状态 ,

    如 : 一个 人 可以 有多种 状态 : 开心 , 生气, 郁闷。

    但如 过你 面试 的时候 面到多态的 问题 ,如果 只说出 这语句 化 多半 要 凉凉。

    想要了解 多态 我们需要 先 了解 这个 几个 方法面

    1.重写

    2.向上转型

    3.动态绑定

    1.重写

    这里 先来 看我们的 代码

    
    class Animal{
        public String name;
        public int age;
        public void eat(){
            System.out.println(name + "吃饭");
        }
    }
    class Dog extends Animal{
    
            
    }
    class Cat extends  Animal{
    
    }
    
    public class Test {
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    这里 Dog 和 Cat 都 继承 了 Animal , 但 我们 觉得 此时 eat 方法 有点不合适 ,狗 吃 狗粮 ,猫 吃 猫粮 , 这里 的 eat 方法中的 吃饭 就有点 草率了 。

    那么 我们 就需要 重新 在 Dog 和 Cat 类 中 将 eat 方法 进行 重写 ,来 完成我们 狗 吃 狗粮, 猫 吃 猫粮 的 操作 。

    在这里插入图片描述


    此时 我们 的 Dog 中 的 eat 方法 和 Animal 中 的 eat 方法 就构成 了 我们 的重写。

    重写 的 要求 :

    1.方法名相同

    2.返回值 相同

    3.参数列表相同(数据类型,个数,顺序 都得相同)

    小细节 :

    1.重写 的 方法 访问 权限不做 要求 ,但是 父类 的 访问权限 要 低于或等于 子类 。

    2.被 private 修饰 是 不能 被 重写的


    下面 就来演示 一下:

    1.父类 是 public 子类必须是 public

    在这里插入图片描述

    2.父类 是 包访问 权限 (什么都没加)

    在这里插入图片描述

    3.被prviate 修饰 的 方法是 不能 被 重写 的

    此时 聪明 的 同学 们 ,就开始 说 ,我直接 父类 加上 个, private 修饰 直接 子类访问 修饰 符 想要啥 要啥了吗 ?

    可惜 很 抱歉 , 我们 的 方法 如果 被 private 修饰 是 不能够 重写的

    在这里插入图片描述


    另外 : 我们 除了 被 private 修饰 的 方法 不能 重写其实 还有 两种 方式 会 导致 父类 方法 不能够 重写 。

    1.被 final 修饰 的 父类方法 也不能够 重写

    演示:

    在这里插入图片描述


    这里 final 修饰 的 方法 我们 叫做 密封 方法 ,不能被 重写。

    2.被 static 修饰的 父类方法 也不能够 重写

    演示:

    在这里插入图片描述


    这里我们 的 注解 报错 了 , 这里 可以 有 人 会 想 我们 删掉 注解 不就 ok了。

    在这里插入图片描述

    可惜 , 这里 我们还是报错 了 。

    这里 就需要我们 注意 : @Override 注解 。 有了这个注解能帮我们进行一些``合法性校验.`

    这 被 static 修饰 重写 本身 就 是 不和 法 的 这里 注解 就会 进行 合法性 校验 , 发现 你这个 错误就 在 注解处报错 了 ,没有 注解 那么 就直接 在 方法上 报错了。

    补充 : 重写 其实 可以 返回值 不同

    我们 上面 刚刚 说了 重写 返回 值 相同 ,这里 就 说 返回值 可以 不同 ,真 的 绕来绕去 , 为啥 这里 又能 返回值 不同呢 。

    这里 就来看一下 :

    在这里插入图片描述

    这里我们 的 返回值 可以 不一样 但是 我们的 返回值 之间 必须 构成父子类 关系。

    上面 差不多就算 重写 的 全部 内容, 下面 来 看看文字 描述 。

    重写(override):

    也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法

    下面 我们 来 看一下 面试 问题 : 重载 和 重写 的 区别 是 什么?

    重载重写
    相同点1.方法名称相同1.方法名称相同
    不同点2.返回值不做 要求2.返回值相同
    构成父子类关系也可以
    不同点3.参数列表不同
    (数据类型 顺序,个数 都不同)
    3.参数列表相同
    (数据的 类型 ,顺序 ,个数,都相同)
    不同点4.在同一个类中 或 不同类 中 都能构成重载4.重写 一定是 发生在 继承层次上(两个 或 以上的 类)
    不同点重载 是 没有权限 要求的5.重写 是 有权限要求的 (子类的权限要大于等于 父类)
    另外父类 被 private 修饰 是不能够重写的

    知道了 重写 ,那么 我们 要如何 去设计 我们的 重写 呢 (换种 说法 啥时候 使用 重写) ?

    重写的 设计 原则

    这里我们 就来 看一下 我们的 【重写的设计原则】

    对于已经投入使用的类,尽量不要进行修改。最好的方式是:重新定义一个新的类,来重复利用其中共性的内容,并且添加或者改动新的内容。

    例如:若干年前的手机,只能打电话,发短信,来电显示只能显示号码,而今天的手机在来电显示的时候,不仅仅可以显示号码,还可以显示头像,地区等。在这个过程当中,我们不应该在原来老的类上进行修改,因为原来的类,可能还在有用户使用,正确做法是:新建一个新手机的类,对来电显示这个方法重写就好了,这样就达到了我们当今的需求了。

    在这里插入图片描述


    看完 设计 原则 , 下面 继续 。


    这里先 抛出 两个 概念 ,先来 学习 一下 静态 绑定 , 动态绑定 在 讲完 向上 转换 讲解。

    静态绑定:也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。典型代表函数重载。

    演示:

    public class Test {
        public static void func(){
    
        }
        public static void func(int a){
    
        }
        public static void func(int a, int b){
    
        }
    
        public static void main(String[] args) {
            func();
            func(1);
            func(1,2);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17


    这里我们编译的 时候 根据 你 传 入 的 参数,能够确定你 调用那个方法 , 这种 就叫做 静态绑定。

    动态绑定:也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用那个类的方法。

    这里我们 要了解 动态绑定 就需要 先了解我们的 向上转型 。

    2.向上转型

    向上转型:实际就是创建一个子类对象,将其当成父类对象来使用。

    在这里插入图片描述

    下面 看代码 :

    class Animal {
        public String name;
        public int age;
    
        public void eat() {
            System.out.println(name + "开行的 吃饭");
        }
    }
    class Dog extends Animal{
    
        public void func(){
            System.out.println("Dog 特有的 方法");
        }
    }
    public class Test {
        public static void main(String[] args) {
            Dog dog =  new Dog();
            // animal 这个 引用 指向了 dog 这个对象 
            Animal animal = dog;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21


    这里 就是 我们的 向上转型。

    注意: 此时 animal 这个引用 虽然 拿到 到了 dog 对象, 但是不能调用 Animal 类 中 没有 ,但 Dog 这个 类 中 特有 的 方法 或 属性。

    在这里插入图片描述

    可以 看到 我们 是不能 将 dog 这个 对象 的 func 方法 给调用出来的。

    这里我们的 Animal 没有 func 这个 方法 所以我们就 访问不了 。

    此时 我们 继续 。

    在这里插入图片描述

    下面就来 了解 我们的 动态绑定 :

    概念 :

    当 子类 重写 了 父类 的 方法 时 , 通过 父类 引用 引用了 子类 对象, 调用 这个被 重写 了 的 方法时 ,就会 发生我们的动态 绑定。

    这里 我们 动态绑定 的 条件 :

    分析 上面这句话 我们能得到几个 关键条件 ,

    1.子类 重写父类 的 方法 .

    2. 父类 引用了子类 对象 。

    3.需要 调用 这个 重写的 方法


    下面我们 通过 观察 反汇编 来 看看 动态绑定 。

    在这里插入图片描述


    下面 我们 继续 回到 我们的 向上转型 。

    向上转型的 两种 方式 :

    1.直接赋值 (刚刚我们写 的 就是 直接 赋值 )

    在这里插入图片描述

    2.方法 传参

    class Animal {
        public String name;
        public int age;
    
        public void eat() {
            System.out.println(name + "开行的 吃饭");
        }
    }
    
    class Dog extends Animal {
        @Override
        public void eat() {
            System.out.println("重写 的 eat 方法");
        }
    
        public void func() {
            System.out.println("Dog 特有的 方法");
        }
    
    }
    
    public class Test {
        public static void func(Animal animal){
            animal.eat();
        }
    
        public static void main(String[] args) {
            Dog dog = new Dog();
            func(dog);
        }
    }
    
    • 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

    在这里插入图片描述

    这里 方法 传值 的 过程 中 , 就发生 了 向上 转型 , (传入 的 对象 是 子类 ,方法 接收 的 是 父类 引用)。

    多态


    介绍 到现在 , 接下来 一个 代码 就能 让 大家 立马 知道 多态是 是什么 。

    请看:

    class Animal {
        public String name;
        public int age;
    
        public void eat() {
            System.out.println(name + "开行的 吃饭");
        }
    }
    
    class Dog extends Animal {
        @Override
        public void eat() {
            System.out.println("重写 的 eat 方法");
        }
    
        public void func() {
            System.out.println("Dog 特有的 方法");
        }
    
    }
    
    class Cat extends Animal {
        @Override
        public void eat() {
            System.out.println("重写 的 eat 方法");
        }
    
        public void func() {
            System.out.println("Cat 特有 的方法");
        }
    }
    
    public class Test {
        public static void func(Animal animal) {
            animal.eat();
        }
    
        public static void main(String[] args) {
            Dog dog = new Dog();
            func(dog);
            Cat cat = new Cat();
            func(cat);
        }
    }
    
    • 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
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    在这里插入图片描述

    看完 上面的代码 再来 看 概念 是不是 就 非常 清楚 了

    多态:

    多态是同一个行为具有多个不同表现形式或形态的能力,
    例如:黑白打印机和彩色打印机相同的打印行为却有着不同的打印效果,
    在这里插入图片描述

    • 对象类型和引用类型之间存在着继承(类)/ 实现(接口)的关系;
    • 当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。
    • 如果子类重写了父类的方法,最终执行的是子类覆盖的方法,如果没有则执行的是父类的方法。


    补充 : 第三种 方法 产生 向上 转型 : 作为 返回值

    class Animal {
        public String name;
        public int age;
    
        public void eat() {
            System.out.println(name + "开行的 吃饭");
        }
    }
    
    class Dog extends Animal {
    
        @Override
        public void eat() {
            System.out.println("Dog 中 重写 的 eat 方法");
        }
    
        public void func() {
            System.out.println("Dog 特有的 方法");
        }
    
    
    }
    
    public class Test {
        // 第三种 向上 转型 , 作为 返回 值 。
        public static Animal func(){
            return new Dog();
        }
        
    }
    
    
    • 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


    这里我们 原本 返回的是 Animal 类 行 的 , 但 我们 返回 了 一个 Dog 子类 对象 ,这里 同样 也会 发生 向上 转型。

    有 向上 转型,我们 还有 向下 转型 。

    下面 就来 学习 一下 。

    3.向下 转型


    我们 前面 说过 ,向上 转型 的 引用 是 调用 不了 子类 特有的 属性 或 方法 的 , 那么 我们 要如何 去 调用 子类 特有 的 方法 呢? 这里 我们 就可 以 使用 向下 转型 。

    在这里插入图片描述

    使用 向下 转型 调用 Dog 中 特有 的 func 方法。

    在这里插入图片描述


    这里 就 是 我们 的 向下 转型 , 这里 我们 运行 下 。

    在这里插入图片描述

    这里 就 成功 的 调用 处 Dog 特有 的 特有的 方法 , 但 向下 转型 存在 不安全的 地方 , 这里我们 继续 来 举例子 ,

    在这里插入图片描述


    这里 我们 有 两个 类 , 一个 鸟 类 , 一个 狗 类 。

    当你 完成 下面 操作的 时候 ,你 就会 发现 会报错 。

    在这里插入图片描述


    这里 我们 狗 会 想 鸟 一样飞吗 ? (除掉 那种 一生 只能 飞一次的 可能)。

    原本 我们 向上转型 ,将 Dog 对象 给了 父类 Animal 的 引用, 然后 又 向下 转型 ,将 这个 引用 狗的 对象 , 赋值 给 Dog类型的 引用, 这样是不会 出错的 , 但 赋值 给 鸟 这个 类 的引用 ,此时 就会报错。 但是我们 每 编译 时 是 不能 发现 类型 不匹配 的 , 只有 编译 后 才能 发现 。

    那么 要如何 解决 这个 问题 呢?

    此时我们 就需要一个 关键字 来 帮助我们 来判断

    关键字 : instanceof

    在这里插入图片描述

    最后 :我们 一般 不太 使用 向下 转系 了解 即可 。

    关于构造方法的 坑


    在 本文 的 最后 我们 来 看一下 一个 关于 构造 方法 的 坑 .

    观察下面的代码 分析 最后输出 什么 :

    class Animal {
        public String name = "hellow";
        public int age;
        private int count;
    
        public Animal(String name, int age) {
    
            this.name = name;
            this.age = age;
            eat();
        }
    
        public void eat() {
            System.out.println(name + "ani::eat()");
        }
    }
    
    class Dog extends Animal {
        public Dog(String name, int age) {
            super(name, age);
        }
    
        @Override
        public void eat() {
            System.out.println(name + "狼吞虎咽的eat()");
        }
    }
    
    public class TestDome {
        public static void main(String[] args) {
            Dog dog = new Dog("小黑", 18);
        }
    }
    
    
    • 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

    是 ani:: eat() 还是 狼吞虎咽 的 eat() 呢 ?


    答案揭晓:

    在这里插入图片描述

    你的 答案 对 吗?

    下面 就来 梳理 一下 。

    在这里插入图片描述

    本文结束 :下文 预告 抽象类 or 接口

  • 相关阅读:
    Mac如何搭建Vue项目
    如何在功能、特点、价格和性能方面选择PDF编辑器?
    Win10鼠标宏怎么设置?电脑设置鼠标宏的方法
    71页文档统一管理云平台项目建设方案
    字节跳动面试——算法
    教你一招轻松搞定mp3格式转换
    加速通导融合,中国在精准定位领域脱颖而出
    数据转换工具DBT介绍及实操
    基础中的基础!吴恩达deeplearning.ai:如何搭建一个神经网络
    08-Nginx缓存集成
  • 原文地址:https://blog.csdn.net/mu_tong_/article/details/126235960