【Java】弄清多态,看这一篇就够了|由浅入深,保姆级详解

news2024/11/29 8:00:17

在这里插入图片描述

  • 博主简介:努力学习的预备程序媛一枚~
  • 博主主页: @是瑶瑶子啦
  • 所属专栏: Java岛冒险记【从小白到大佬之路】

前言

在上篇【Java】还不理解继承?一篇文章看懂继承|继承入门,我们了解了继承的概念如何时两个类建立继承关系is-a、以及继承中的一些细节

但是,这只是庞大继承体系的一角。今天讲解在继承、封装基础上、方法重写的非常重要的一点—多态 polymorphic.

同时多态也是JAVA第三大重要特性。这点在开篇博客【Java基础篇】Java重要特性,JDK,JRE,JVM区别和联系,环境变量中已经讲到。

目录

  • 前言
  • Part1:基本介绍:
    • 1.1:多态的体现
    • 1.2:对象的多态
  • Part2:编译类型、运行类型
  • Part3:向上转型
    • 3.1:向上转型发生时机
      • 3.1.1:方法传参
      • 3.1.2:方法返回
    • 3.2:注意事项&细节:
  • Part4:动态绑定机制
    • 4.1:介绍
    • 4.2:动态绑定的意义
    • 4.3:注意事项
  • Part5:向下转型
    • 5.1:基本介绍
    • 5.2:使用细节&注意事项
    • 5.3:意义&使用场景
  • Part6:多态的优缺点
    • 6.1:使用多态的好处:
    • 6.2多态的缺陷
  • Part7:总结

Part1:基本介绍:

多态,其实就是指“一种”事物,可以有多种形态。比如之前讲到的方法重载,是多态(函数名相同,参数列表不同,但功能含义相同);还比如方法的重写(方法相同,但在父子类之中的实现不同);对象的多态,是指一个对象可以有不同的形态(类型)。那究竟什么是对象的多态呢?又是如何实现的。接下来我们来系统学习一下。

1.1:多态的体现

Java中的多态分为两个方面:

  • 方法的多态

    • 重载【Java】保姆级讲解|从0到1学会方法及方法重载 ( 入门,包懂)
    • 重写【Java】弄清方法重写,看这一篇就够了|由浅入深,保姆级讲解
  • 对象的多态

1.2:对象的多态

🤷‍♀️ 对多态的简单理解:父类引用可以指向子类对象,且用该父类引用去调用子类重写过的实例方法时,不同的对象会产生不同的状态(动态绑定)。

🌷Java中对象多态实现的条件(缺一不可)

  • 必须在继承体系下
  • 子类必须要对父类中方法进行重写(父类引用当然还是可以接收子类对象引用的,但这个时候其实体现不出来多态)
  • 通过父类引用调用重写方法

举例:

/**
 * Created with IntelliJ IDEA.
 * Description:
 * Author: yaoyao2024
 * Date: ${YEAR}-${MONTH}-${DAY}
 * Time: ${TIME}
 */
class Animal {
    String name;
    int age;

    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void eat() {
        System.out.println(this.name + "eating");
    }
}


class Cat extends Animal {
    String name;
    int age;

    public Cat(String name, int age) {
        super(name, age);
        this.name = name;
        this.age = age;
    }

    public void eat() {
        System.out.println(this.name + "吃小鱼干~");
    }
}

class Dog extends Animal {
    String name;
    int age;

    public Dog(String name, int age) {
        super(name, age);
        this.name = name;
        this.age = age;
    }

    public void eat() {
        System.out.println(this.name + "吃骨头~");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal1 = new Cat("咪咪", 2);
        Animal animal2 = new Dog("旺旺", 3);
        animal1.eat();
        animal2.eat();
    }

}

在这里插入图片描述

可以看到,虽然animal1animal2是桶属于Animal类型,但调用方法时,看似都是eat()方法,但实现效果不同,是调用实际类型(CatDog)各自的方法,这种一个类型对象,但是调用不同方法,呈现出不同行为,就是多态!!!

从这个例子我们也知道:当用父类引用指向子类对象时,调用重写方法,是调用子类中重写过的方法而不是父类当中的方法!

在内存中,父类引用和子类对象实体是如下关系:
在这里插入图片描述

Part2:编译类型、运行类型

再深入学习多态时,我们不得不先了解两个概念:编译类型、运行类型。这同时也是待会我们讲动态绑定的基础。

先看这句话:

//变量类型 变量名   对象
Animal animal01 = new Cat();
  • 等号左边:编译类型( Animal )
    • 解释:所谓编译类型,就是在编译时期确定的类型。通俗来说,就是编译器认为这个变量是什么类型。比如int a = 10就是告诉计算机,a这个变量的类型是int,这时在编译的时候编译器就确定好的。
    • 引用变量的类型在编译时确定(无可厚非,变量声明时都有类型,向计算机声明,这个变量是什么类型,这是在编译时即确定好了)
  • 等号右边:运行类型( Cat )
    • 为什么叫作运行类型呢?因为new 对象这条语句是在运行时期执行的,对象是什么类型,也是运行时期确定的。
    • 运行类型是对象的实际类型。即这个对象本质上是Cat,用Animal来表示,以提高代码通用性

如何理解呢?
变量是一个盒子,编译类型决定了这个盒子长什么样子(计算机如何去理解&看待),而实际类型决定了这个盒子里面放的是什么东西。

编译时描述了这个盒子的样子、类型,编译器只知道:这是一个放动物的盒子,所以你决定把猫咪放进去,它不报错 Animal animal = new Cat()(注意,这个时候,即程序没有运行之前,还没有把实际的猫咪放进去)

而运行时期,是真的创造了这只小猫咪,并且放进去。所以animal的实际类型/运行类型是:Cat

C++ 说: 由于编译时决定了 指针长度是父类,所以解析的时候就按照父类指针要求去解析

【补充】:instanceof–比较操作符,用于判断对象的运行类型是否为XX类型/XX类型的子类型:返回值是boolean类型

System.out.println(Cat instanceOf Animal);//true

Part3:向上转型

介绍
就是一种类型转换,相当于把子类对象实体的类型由子类类型,向上转换成父类类型。

为什么要叫向上呢?首先是继承的本质其实就是一种由子类,向上的逐级查找关系。其次,在绘制UML图,来表示类与类之间的关系时,我们都习惯将父类画在子类上方。所以我们就把这种类型转换称为“向上转型”,十分形象,表示:从子类向上,转换成父类。

写法:

父类类型 变量名 = new 子类类型();
eg:
Animal animal01 = new Cat();

理解
如何去理解这种这种向上转型呢?–is a

3.1:向上转型发生时机

向上转型发生的时机分为以下三种:

  • 直接赋值
  • 方法传参
  • 方法返回

前文所讲的一直都是直接赋值,也很好理解。这里详细讲讲后面两种。

3.1.1:方法传参

public class Main {
    public static void main(String[] args) {
        Cat cat01 = new Cat();
        feed(cat01);
    }
    public static void feed(Animal animal) {
        animal.eat();
    }
}

其实传参的本质也是赋值:Animal animal = cat01;

3.1.2:方法返回

public class Main {
    public static void main(String[] args) {
        Animal animal = gain();
    }
    
    public static Animal gain(){
        return new Dog();
    }
}

返回类型是Animal,但返回对象的实际类型是Dog。在返回的时候进行了向上转型,把Dog类对象转换为父类Animal并返回

3.2:注意事项&细节:

父类引用指向子类对象时:(即发生向上转型时)

  • 该引用可以调用父类中的所有成员(但是必须遵守子类调用父类属性、方法的访问权限规则)
  • 不能调用子类的特有成员
    因为编译类型在编译时期就确实了,决定了计算机认为你这个引用类型就是父类的,用父类引用去调用子类的特有成员当然是错误的,编译时就会报错。
  • 该引用调用父类方法时,最终实现(运行效果),要先看子类是否重写

在这里插入图片描述

  • 该引用访问属性时,直接访问的是父类属性,不看子类。因为属性不可重写!

【总结】:调用方法看运行类型,访问属性看编译类型

Part4:动态绑定机制

在之前的文章中,我们讲了方法重写,也提到,方法重写和动态绑定的本质是一样的。只是动态绑定是方法重写的底层实现。

这里,既然编译类型和运行类型都已经讲了,我们现在来着重讲一下动态绑定:

注意!:动态绑定是方法重写的原理,是基于实例方法的,对于属性、静态方法、构造器不存在动态绑定这一说!

4.1:介绍

当对象引用调用实例方法时,该方法会和对象的内存地址/实际运行类型绑定

4.2:动态绑定的意义

方法重写和动态绑定的本质一样,所以动态绑定的意义其实也就是方法重写的意义。

这里结合多态再次说一下,希望大家能对多态&动态绑定有更好的理解。

那么为什么需要多态&动态绑定呢?

因为创建子类对象代码和操作子类对象代码通常情况下,并不是向我们举得例子那么简单,挨的那么近,而是经常不在一起。操作对象的代码往往不知道该对象的实际类型,往往只知道其父类类型。往往也只需要知道它是某种父类型即可
(再通俗来说:把确定对象实际运行类型,以及根据实际运行类型调用相应方法的工作交给了底层,JVM去帮我们完成)

public class Main {
    public static void main(String[] args) {
        Animal animal01 = new Cat();
        Animal animal02 = new Dog();
        Animal[] animals = {animal01, animal02};
        for (Animal animal : animals) {
            animal.eat();
        }
    }
}

一句话来说就是:方便统一管理、操作不同子类型对象,同时又能实现对象的特有行为–两全其美哉~

4.3:注意事项

当父类引用指向子类对象时,不可用父类引用去调用子类对象的特有方法,否则会报错

理解:因为声明是父类,编译器会去父类中找这个方法,如果找不到,则编译报错。

Part5:向下转型

5.1:基本介绍

学习了向上转型,向下转型其实也很好理解。

但是注意:向下转型是建立在向上转型基础上的。即,先有向上转型,才能有向下转型,不能直接把父类对象转成子类型!

//这是向上转型
父类类型 变量名 = new 子类类型();
Animal animal01 = new Cat();

//这是向下转型:
子类类型 变量名 (子类类型)父类引用
Cat cat01 = (Cat)animal;

5.2:使用细节&注意事项

  • 在强转父类引用之前,该父类引用必须指向子类型对象
  • 强转后,可以用该引用调用子类所有成员

5.3:意义&使用场景

向下转型其实是对向上转型的一种弥补,向上转型(基于继承、多态、动态绑定)后,有很多好处。但是我们也知道,无法通过父类引用访问子类特有属性和调用子类的特有方法。那么如何保证既可以统一管理子类型(降低耦合),在需要调用子类型特有属性时和方法时,可以调用到呢?

什么时候向下转型:

  • 需要获得运行类的属性

  • 需要调用运行类的特有方法

举例:(用到的类不变)

public class Main {
    public static void main(String[] args) {
        show(cat01)}

    public static void show(Animal animal) {
        if(animal instanceof Cat){
            ((Cat) animal).climb();
        }
    }
}

Tips:强转要合理,如果把实际运行类型是Cat的强转成Dog,势必要报错。所以出于安全性,在强转之前使用instanceof关键字来判断一下!

Part6:多态的优缺点

6.1:使用多态的好处:

  • 降低代码的“圈复杂度”,避免使用大量if-else

圈复杂度:描述代码复杂程度的方式。一段代码里面条件分支和循环语句越多,那么圈复杂度越高。
简单粗暴理解就是一段代码中条件语句和循环语句出现的个数就是“圈复杂度”,如果一个方法的圈复杂度太高,就需要考虑重构。
不同公司对圈复杂度都有各自的规范,一般不会超过10

这个当然好理解,用同一父类接收多个不同类型子类,不用判断类型,因为用父类引用调用重写方法是子类各自的重写过的方法!

  • 可扩展能力强

    当我们想要多加一个动物类型时,可以直接创建类和这个类的实例

6.2多态的缺陷

  • 属性没有多态性

    也就是说,当父类和子类有同名属性时,通过父类引用只能调用父类自己的属性
  • 在父类构造方法中调用重写方法有坑!

class Parent {
    public Parent() {
        func();
    }

    public void func() {
        System.out.println("parent.func()");
    }
}

class Child extends Parent {
    private final int num = 1;

    public void func() {
        System.out.println("child.func()" + num);
    }
}

public class Main {
    public static void main(String[] args) {
        Child child = new Child();
    }
}

结果为child.func():0,原因是创建子类时调用父类构造方法,而在父类构造方法中调用了子类重写的方法,此时触发动态绑定,调用子类的func,但是此时子类child还没有完成初始化,num为0!

结论:“用尽量简单的方法使对象进入可工作状态”,尽量不要在构造器中调用方法。因为如果此时子类构造还没完成,就会触发动态绑定,这样可能会出现一些隐藏但是极难发现的问题!

Part7:总结

多态的核心就是让调用者不必关心对象的具体类型。降低使用成本,提高开发效率。

此篇文章讲的多态是建立在继承继承基础之上。其实文章开篇也有讲到,多态其实就是多种形态,是一个比较广泛的概念。在后面的接口抽象类中,还会用到。


在这里插入图片描述

  • Java岛冒险记【从小白到大佬之路】

  • LeetCode每日一题–进击大厂

  • Go语言核心编程

  • 算法

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/741561.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AndroidUI绘制流程

Android源码阅读 UI绘制流程 环境 Java 11android 11 由于学习的课程api 不一致 导致源码有些关键方法无法进入仔细阅读 采用截图的方式理解思路 view添加到窗口 进入到源码中可以发现 ,每个activity 默认生成的代码中都会有一个setContentView方法&#xff0c…

Python采集课堂视频教程, m3u8视频解密

前言 嗨喽,大家好呀~这里是爱看美女的茜茜呐 环境使用: Python 3.8 解释器 Pycharm 编辑器 模块使用: requests >>> pip install requests pycryptodome --> pip install pycryptodome re 第三方模块安装方法: win R 输…

SpringBoot+MinIO实现minio部署和使用

Minio是一个go编写基于Apache License v2.0开源协议的对象存储系统,是为海量数据存储、人工智能、大数据分析而设计,它完全兼容Amazon S3接口,十分符合存储大容量的非结构化数据从几十kb到最大5T不等。是一个小而美的开源分布式存储软件。 特点 简单、可…

windows系统下载大白菜制作iso镜像文件

背景 1. ventory制作了U盘启动盘,ventory只能加载iso文件, 我们有些操作需要进入到winpe系统进行操作,故需要制作大白菜pe的iso文件 操作步骤 下载大白菜 大白菜u盘启动盘制作工具_大白菜u盘装系统_大白菜pe_大白菜官网-首页 (windowsrw.…

一个判断 I2C 总线通信异常原因的方法

一个判断 I2C 总线通信异常原因的方法 参考链接 【经验分享】一个判断 I2C 总线通信异常原因的方法 (stmicroelectronics.cn)https://shequ.stmicroelectronics.cn/thread-633302-1-1.html 至于如何在i2c总线中如何使用还没有弄明白。后续再看。 使用方法记录 阻值分配 图中…

Confidence Regularized Self-Training 阅读笔记

Confidence Regularized Self-Training 领域自适应研究的最新进展表明,深度自训练是实现无监督领域自适应的有效手段。这些方法通常涉及到一个迭代过程,即在目标域上进行预测,然后将自信的预测作为伪标签进行再训练。然而,由于伪…

泛微打造国资委国企双端的监管平台,数据互联,动态管理

数字政府和数字监管成为趋势。数字化方式能够助力完善国有资产管理体制,促进国有资产保值增值,推动国有资本做强做优做大,有效防止国有资产流失。 国资监管过程中存在着诸多挑战 监管企业众多,需要建立不同的管理模式。既要发挥…

C/C++库函数之——str类和mem篇(常用速学)

目录 一,str类 1)strlen 2)strcpy 3)strcmp 4)strcat 5)strstr 二,mem类函数 1)memcpy 2)memmove 一,str类 1)strlen 用途&#xff1a…

光伏5G多合一融合终端|光伏多合一融合终端|光伏多合一群调群控网关|分布式光伏群控群调|光伏AGC/AVC系统这几者之间什么技术关系,多少钱一套预算?

光伏5G多合一融合终端|光伏多合一融合终端|光伏多合一群调群控网关|分布式光伏群控群调|光伏AGC/AVC系统这几者之间什么技术关系,多少钱一套预算? 一:光伏5G多合一融合终端的功能 光伏5G多合一融合终端的功能:群调群控/AGC/AVC功…

[MMDetection]生成测试集预测的test.bbox.json文件

基于MMdetection3.10 困扰了大半天的问题,终于解决了。 方法1:定位到configs\_base_\datasets\coco_detection.py 将里面的路径全部换为自己的路径,最重要的是将以下注释取消掉,特别注意以下两个参数 改好的文件示例 # datase…

数据结构 - 线性表(C语言版)

线性表分为顺序表和单链表 线性表的操作主要是查询、插入、删除 1、顺序表 首先,定义一个顺序表的结构体 #define MAX_SIZE 10 typedef struct {int data[MAX_SIZE];int length; }SqList, * PsqList;创建一个线性表 void createSqList(PsqList pSqList) {pSqLis…

Spring—事务及事务的传播机制

Spring—事务及事务的传播机制 🔎事务的定义🔎Spring—事务的实现铺垫Spring 编程式事务Spring 声明式事务Transactional 的参数注意事项Transactional 的工作原理 🔎Spring—事务的隔离级别MySQL—事务的隔离级别Spring—事务的隔离级别Spri…

剑指 Offer 04. 二维数组中的查找(java)

二维数组中的查找 剑指 Offer 04. 二维数组中的查找题目描述抽象 BST 解题 二叉树专题 剑指 Offer 04. 二维数组中的查找 来源:力扣(LeetCode) 链接:https://leetcode.cn/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof 题目描述…

js为啥是设计成单线程而不是多线程呢

了解这个问题之前,需要先了解一下以下问题: 什么是进程?什么是线程?二者有啥关联?任务队列是什么?宏任务?微任务?eventloop?为什么说js是单线程,为什么要设计…

UWB高精度定位标签方案,厘米级室内测距,实时人员物品位置确定

随着科技的不断进步,UWB技术正逐渐成为各个领域的定位解决方案的首选。其高精度、安全、实时的特性使其在安全免提访问控制、实时室内定位等应用领域发挥着重要的作用。 安全免提访问控制是目前应用UWB技术的重要领域之一。通过将UWB标签(如手机、钥匙扣…

Django如何删除数据库表中的数据【不断积累】

这篇博客积累Django的数据库常用删除方法。 假设有表模型Author定义如下: class Author(models.Model):name models.CharField(max_length100)def __str__(self):return self.name01-根据记录的id号来删除指定的记录 Django 默认为每个模型添加一个名为 id 的自…

RPA赋能日化产业,实在智能广东日化共推行业数字化转型

广东日化,因其独特的地域、产能优势,成为广东制造业的支柱产业之一,占据了全国日化行业的半壁江山。作为本土最具影响力商会组织之一,广东省日化商会凝聚了一批具有影响力的日化企业,其经济总量、市场占有率、品牌知名…

python网站创建:初识网站(001)

1. 初识网站:首先来认识一下,前端、后端、数据库它是怎么分工合作来形成网站的 使用python创建网站之前,需要先稍微认识一下两个最流行python web框架:(Flask)和(Django) Flask是一个轻量级的框架,适用于比较轻巧&…

【*1900倍数遍历】CF1627 D

Problem - D - Codeforces 题意&#xff1a; 思路&#xff1a; 在枚举数列子集的gcd时&#xff0c;通常可以枚举倍数 对于这道题要注意&#xff0c;j/i的gcd要为1&#xff0c;这样才能保证i是这个子集的最大公约数 Code&#xff1a; #include <bits/stdc.h>//#define…

三菱FX3U简单工程编程

1.简单工程编程 1.1.元件 常开触点&#xff08;ld&#xff09; 选中位置&#xff0c;点击图标&#xff0c;输入软元件&#xff0c;完成添加。 选中位置&#xff0c;快捷键F5添加。 选中位置&#xff0c;输入ld 软元件添加。常闭触点&#xff08;ldi&#xff09;横线、竖线 …