Java多态详解
下面讲解一下Java中的多态机制,力求用最通俗易懂的语言,最精炼的话语,最生动的例子,深入浅出Java多态,帮助读者轻松掌握这个知识点。
什么是多态?
多态是指同一种行为具有多个不同表现形式的能力。
多态的分类
多态一般分为重载式多态和重写式多态:
- 重载式多态,也叫编译时多态。也就是说这种多态在编译时已经确定好了。方法名相同而参数列表不同的一组方法就是重载。在调用这种重载的方法时,通过传入不同的参数最后得到不同的结果。
- 重写式多态,也叫运行时多态。这种多态通过动态绑定(dynamic binding)技术来实现,是指在运行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。也就是说,只有程序运行起来,你才知道调用的是哪个子类的方法。这种多态通过方法重写以及向上转型来实现。
多态实现的必要条件
- 有继承/实现关系:在多态中必须存在有继承关系的子类和父类或者接口及其实现类。
- 有方法重写:子类对父类中某些方法进行重新定义,再调用这些方法时就会调用子类的方法。
- 有父类引用指向子类对象:父类引用指向子类对象叫做向上转型。
向上转型和向下转型
- 向上转型:父类引用指向子类对象,这个是自动的,不需要显示转换。通过向上转型,你可以调用在父类中定义的方法,但不能调用子类特有的方法。
- 向下转型:子类引用指向父类对象,这个是非自动的,需要进行强制类型转换。在进行向下转型之前,通常需要使用
instanceof
操作符来检查引用的对象是否确实是目标子类的实例。
多态的成员访问特点
- 成员变量:编译看左边(父类),执行看左边(父类)
- 成员方法:编译看左边(父类),执行看右边(子类)
举个栗子:
// 水果类,拥有一个show()方法
public class Fruits {public void show() {System.out.println("我水果之父,打钱!");}
}
// 苹果类,实现父类水果,并重写show()方法
public class Apple extends Fruits{@Overridepublic void show() {System.out.println("我苹果,打钱!");}public void color() {System.out.println("我是红色的苹果。");}
}
// 香蕉类,实现父类水果,并重写show()方法
class Banana extends Fruits {@Overridepublic void show() {System.out.println("我香蕉,打钱!");}public void color() {System.out.println("我是黄色的香蕉。");}
}
// 测试类
public class Test {public static void main(String[] args) {Fruits fruit = new Apple(); // 向上转型fruit.show(); // 我苹果,打钱!fruit = new Banana(); // 向上转型fruit.show(); // 我香蕉,打钱!}
}
这就是向上转型,Fruits fruit = new Apple();
将子类对象Apple转化为父类对象Fruits,这个时候fruit引用指向的是子类对象,所以调用的方法是子类方法。
需要注意的是:向上转型时,子类单独定义的方法会丢失。比如,上面案例中的Apple类和Banana类都定义了自己的color方法,当进行了向上转型后,fruit引用指向Apple类的实例时是访问不到color方法的,fruit.color()
会报错。
下面给出原因:
我们需要时刻记住多态的成员访问特点:编译时看左边,运行时看右边。
比如Fruits fruit = new Apple();
,这行代码中的变量fruit在编译时和运行时的类型如下:
- 编译时 (Compile-time):编译时看左边,变量fruit的类型就是Fruits,这是因为我们声明了fruit为Fruits类型。在编译时,编译器只知道fruit是一个Fruits类型的引用,因此它只允许调用在 Fruits类中定义的方法,而不允许调用子类Apple中特有的方法,除非进行向下转型。
- 运行时 (Run-time):运行时看右边,fruit实际指向的对象是Apple类型的实例。这是因为我们使用new Apple()创建了一个Apple类型的对象并将其引用赋值给了fruit。因此,当我们调用 fruit.show()时,实际执行的是Apple类中重写的show()方法。
总结:在编译时,fruit的类型是Fruits,这决定了我们可以对fruit调用哪些方法。在运行时,fruit实际指向的对象是Apple类型,这决定了当我们调用fruit的方法时,实际执行的是哪个版本的方法(即Fruits类中的原始方法还是Apple类中的重写方法)。
上面讲的是向上转型,下面我们来讲一下向下转型:
// 测试类
public class Test {public static void main(String[] args) {Fruits fruit = new Apple(); // 向上转型fruit.show(); // 我苹果,打钱!fruit = new Banana(); // 向上转型fruit.show(); // 我香蕉,打钱!if (fruit instanceof Banana) {Banana banana = (Banana) fruit; // 向下转型banana.color(); // 我是黄色的香蕉。}}
}
注意:在进行向下转型之前,使用instanceof
操作符进行检查是很重要的,否则,如果对象不是正确的类型,转型会抛出异常。
当使用instanceof
关键字进行类型检查时,它会查看对象的运行时类型,而不是编译时类型。比如上面的例子中,fruit instanceof Banana
这里fruit的引用在运行时指向一个Banana类型的对象,所以这个表达式的结果是true。简而言之,instanceof
关键字总是基于对象的实际运行时类型来进行判断。
在向下转型中,子类引用指向父类对象(父类型,实例是子类的实例化),通常需要进行强制类型转换,但是这里有个需要注意的问题。
// 测试类
public class Test {public static void main(String[] args) {Fruits fruit = new Apple(); // 向上转型Apple apple = (Apple) fruit; // 向下转型,强制类型转换apple.color(); // 我是红色的苹果。Banana banana = (Banana) fruit; // 报错:java.lang.ClassCastExceptionFruits f1 = new Fruits();Apple a1 = (Apple) f1; // 报错:java.lang.ClassCastException}
}
为什么Apple apple = (Apple)fruit;
没有报错可以转换成功呢?因为apple本身就是Apple对象,所以理所当然可以向下转型为Apple,因此自然也就不能转换成Banana,人可以干出指鹿为马的事情,但是编译器不行,不会指着苹果说是香蕉。
而f1是Fruits对象,它也不能被向下转型为任何子类对象,就好比你买了一个不知名的水果,你只知道它是一种水果,但是你不能直接说这个水果是苹果或者香蕉。
总结一下向下转型需要注意的问题:
- 向下转型的前提是父类引用指向的是子类对象,也就是说,向下转型之前,它得先进行过向上转型。
- 向下转型只能转型为本类对象(苹果是不能变成香蕉的)。
最后来看一个多态的经典案例:
public class A { // A类public String show(D obj) {return ("A and D");}public String show(A obj) {return ("A and A");}
}
public class B extends A { // B类public String show(B obj){return ("B and B");}public String show(A obj) {return ("B and A");}
}
public class C extends B { // C类
}
public class D extends B { // D类
}
// 测试类
public class Test {public static void main(String[] args) {A a1 = new A();A a2 = new B();B b = new B();C c = new C();D d = new D();System.out.println("1--" + a1.show(b)); // 1--A and ASystem.out.println("2--" + a1.show(c)); // 2--A and ASystem.out.println("3--" + a1.show(d)); // 3--A and DSystem.out.println("4--" + a2.show(b)); // 4--B and ASystem.out.println("5--" + a2.show(c)); // 5--B and ASystem.out.println("6--" + a2.show(d)); // 6--A and DSystem.out.println("7--" + b.show(b)); // 7--B and BSystem.out.println("8--" + b.show(c)); // 8--B and BSystem.out.println("9--" + b.show(d)); // 9--A and D}
}
前三条输出语句还好理解,从第四条开始,为什么不是输出4–B and B而是4–B and A呢?
网上博客给的一句话:当父类对象引用变量引用子类对象时,被引用对象的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在父类中定义过的,也就是说被子类覆盖的方法。这句话对多态进行了一个概括,其实在继承中对象方法的调用存在一个优先级:this.show(O)
、super.show(O)
、this.show((super)O)
、super.show((super)O)
。
这句话有点长,很抽象,我用最通俗易懂的来理解:
在这里,a2
是一个类型为 A
的引用,但它实际上引用的是一个 B
类型的对象。因此,当我们调用 a2.show(b)
时,以下是发生的事情:
- 编译器首先查看引用
a2
的编译时类型,即A
。它会检查类A
中是否有一个接受B
类型参数的show
方法。但是,类A
中并没有明确接受B
类型参数的show
方法。因此,编译器会选择一个更为通用的版本,即show(A obj)
,因为B
是A
的子类。 - 在运行时,JVM会查看
a2
实际引用的对象类型,即B
。由于B
类重写了show(A obj)
方法,因此JVM会调用B
类中的这个版本,即B and A
。
换句话说,A a2 = new B();
这行代码进行了向上转型,前面说过向上转型之后,子类单独定义的方法会丢失(即B
类中的show(B obj)
不能被调用),那么这个时候a2
可以调用的方法就剩下A类中的show(D obj)、show(A obj)
以及B
类中的show(A obj)
。然后再根据我们的口诀“编译时看左边,运行时看右边”,当运行时a2
引用的就是B
对象,故最终a2.show(b)
就是在调用B
类中的show(A obj)
。
接下来再分析第五条a2.show(c)
:
首先A a2 = new B();
进行向上转型,那么,a2
能调用的方法还是A
类中的show(D obj)、show(A obj)
以及B
类中的show(A obj)
,按照继承链中调用方法的优先级,a2
是A
类型的引用变量,所以继承链方法调用优先级中this.show(O)
的this就代表了A
,显然A
类中的方法不满足这个要求,跳过,所以接下来是super.show(O)
,A
类没有父类(除了Object类),再次跳过,然后是this.show((super)O)
,C
继承于B
,B
继承于A
,所以show(A obj)
满足要求,由于a2
变量引用的对象类型是B
类型,而B
类型又重写了该方法,所以最终调用的是B
类中的show(A obj)
,所以最后输出为B and A
。
剩下的输出结果依次分析即可。
至此我们已经完整讲完Java多态机制,每天一个小知识点,每天进步一点点。
相关文章:
Java多态详解
下面讲解一下Java中的多态机制,力求用最通俗易懂的语言,最精炼的话语,最生动的例子,深入浅出Java多态,帮助读者轻松掌握这个知识点。 什么是多态? 多态是指同一种行为具有多个不同表现形式的能力。 多态…...
Android中简单实现Spinner的数据绑定
Android中简单实现Spinner的数据绑定 然后声明对象实例并加入到arraylist里面,并设置spinner的适配器 Spinner Sp (Spinner).............// List<CItem > lst new ArrayList<CItem>(); CItem ct new CItem ("1","测试"); lst.Add(ct)…...

【版本控制工具二】Git 和 Gitee 建立联系
文章目录 前言一、Git 和 Gitee 建立联系1.1 任意目录下,打开 git bash 命令行,输入以下命令生成公钥1.2 配置SSH公钥1.3 进行全局配置 二、其它相关Git指令2.1 常用指令2.2 指令操作可能出现的问题 三、补充3.1 **为什么要先commit,然后pull…...

最新AI智能创作系统ChatGPT商业源码+详细图文搭建部署教程+AI绘画系统
一、AI系统介绍 SparkAi创作系统是基于国外很火的ChatGPT进行开发的Ai智能问答系统。本期针对源码系统整体测试下来非常完美,可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署AI创作ChatGPT?小编这里写一个详细图文教程吧&am…...
【算法与数据结构】--目录
第一部分:算法基础 第一章:算法入门第二章:数据结构概述第三章:算法设计与分析 3.1 贪心算法3.2 动态规划3.3 分治算法3.4 回溯算法 第二部分:常见数据结构 第四章:数组和链表 4.1 数组4.2 链表4.3 比较…...

爱普生LQ1900KIIH复位方法
爱普生EPSON 1900KIIH是一部通用针式打印机,136列(10cpi下)的打印宽度,缓冲区128KB,打印速度为270字/秒。 打印机类型 打印方式:24针击打式点阵打印、打印方向:双向逻辑查找、安全规格标准&am…...

字段位置顺序对值的影响
Unity中验证AB加载场景时报错: Cannot load scene: Invalid scene name (empty string) and invalid build index -1 报错原因是因为把字段放在了Start函数后面(图一)改成(图二)就好了。图一中协程使用的sceneBName字段值为null。 图一: 图二:…...

pytorch_神经网络构建2(数学原理)
文章目录 深层神经网络多分类深层网络反向传播算法优化算法动量算法Adam 算法 深层神经网络 分类基础理论: 交叉熵是信息论中用来衡量两个分布相似性的一种量化方式 之前讲述二分类的loss函数时我们使用公式-(y*log(y_)(1-y)*log(1-y_)进行误差计算 y表示真实值,y_表示预测值 …...

Oracle SQL Developer 中查看表的数据和字段属性、录入数据
在Oracle SQL Developer中,选中一个表时,右侧会列出表的情况;第一个tab是字段的名称、数据类型等属性; 切换到第二个tab,显示表的数据; 这和sql server management studio不一样的; 看一下部门…...

java docker图片叠加水印中文乱码
java docker图片叠加水印中文乱码 技术交流博客 http://idea.coderyj.com/ 1.由于项目需要后端需要叠加图片水印,但是中文乱码,导致叠加了之后 中文是框框 2.经过多方查找基本都说在 linux下安装字体就解决了,但是尝试了均无效 3.后来忽然想到我的项目是用docker打包部署的,不…...

string类的使用方式的介绍
目录 前言 1.什么是STL 2. STL的版本 3. STL的六大组件 4.STL的缺陷 5.string 5.1 为什么学习string类? 5.1.1 C语言中的字符串 5.2 标准库中的string类 5.3 string类的常用接口的使用 5.3.1 构造函数 5.3.2 string类对象的容量操作 5.3.3 string类对象…...

FFmpeg 命令:从入门到精通 | 命令行环境搭建
FFmpeg 命令:从入门到精通 | 命令行环境搭建 FFmpeg 命令:从入门到精通 | 命令行环境搭建安装 FFmpeg验证 FFmpeg 是否安装成功 FFmpeg 命令:从入门到精通 | 命令行环境搭建 安装 FFmpeg 进入 FFmpeg 官网: 点击 Download&#…...

《从零开始学ARM》勘误
1. 50页 2 51页 3 236页 14.2.3 mkU-Boot 修改为: mkuboot 4 56页 修改为: 位[31:24]为条件标志位域,用f表示; 位[23:16]为状态位域,用s表示; 位[15:8]为扩展位域&…...

10款录屏软分析与选择使用,只看这篇文章就轻松搞定所有,高清4K无水印录屏,博主UP主轻松选择
录屏软件整理 如下为录屏软件,通过思维导图展示分析介绍: https://www.drawon.cn/template/details/6522bd5e0dad9029a0b528e1 如下为整理的录屏软件列表 名称产地价格支持的平台下载地址说明OBS国外免费开源windows/linux/machttps://obsproject.co…...
android: android:onClick=“@{() -> listener.onItemClick(viewModel)}“
一、前言:在我使用editTest控件的时候,它的下方有一条横线。我想把它去掉然后我在布局文件中这样写 android:background"null" 导致报错,报错信息是: android:onClick"{() -> listener.onItemClick(viewModel)…...

温故知新:dfs模板-843. n-皇后问题
n−n−皇后问题是指将 nn 个皇后放在 nnnn 的国际象棋棋盘上,使得皇后不能相互攻击到,即任意两个皇后都不能处于同一行、同一列或同一斜线上。 现在给定整数 nn,请你输出所有的满足条件的棋子摆法。 输入格式 共一行,包含整数 n…...
刷题笔记28——一直分不清的Kruskal、Prim、Dijkstra算法
图算法刷到这块,感觉像是走了一段黑路快回到家一样,看到这三个一直分不太清总是记混的名字,我满脑子想起的是大学数据结构课我坐在第一排,看着我班导一脸无奈,心想该怎么把这个知识点灌进木头脑袋里边呢。有很多算法我…...
Mysql时间同步设置
Mysql时间同步设置 当涉及到设置MySQL数据库时间与电脑同步时,实际的步骤可能会因操作系统和数据库版本的不同而有所差异。以下是一个基本的步骤示例,供您参考: 检查电脑时间: 首先确保电脑操作系统的时间是正确的。 设置MySQL时…...

如何理解分布式锁?
分布式锁的实现有哪些? 1.Memcached分布式锁 利用Memcached的add命令。此命令是原子操作,只有在key不存在的情况下,才能add成功,也就意味着线程得到了锁。 2.Reids分布式锁 和Memcached的方式类似,利用Redis的setn…...

windows 远程连接 ubuntu桌面xrdp
更新 sudo apt update安装组件 sudo apt-get install xorg sudo apt-get install xserver-xorg-core sudo apt-get install xorgxrdp sudo apt install xfce4 xfce4-goodies xorg dbus-x11 x11-xserver-utilsxrdp sudo apt install xrdp sudo systemctl status xrdp sudo …...

蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练
前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1):从基础到实战的深度解析-CSDN博客,但实际面试中,企业更关注候选人对复杂场景的应对能力(如多设备并发扫描、低功耗与高发现率的平衡)和前沿技术的…...
【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表
1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

【项目实战】通过多模态+LangGraph实现PPT生成助手
PPT自动生成系统 基于LangGraph的PPT自动生成系统,可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析:自动解析Markdown文档结构PPT模板分析:分析PPT模板的布局和风格智能布局决策:匹配内容与合适的PPT布局自动…...

江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命
在华东塑料包装行业面临限塑令深度调整的背景下,江苏艾立泰以一场跨国资源接力的创新实践,重新定义了绿色供应链的边界。 跨国回收网络:废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点,将海外废弃包装箱通过标准…...
sqlserver 根据指定字符 解析拼接字符串
DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...
06 Deep learning神经网络编程基础 激活函数 --吴恩达
深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...
return this;返回的是谁
一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请,不同级别的经理有不同的审批权限: // 抽象处理者:审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...
虚拟电厂发展三大趋势:市场化、技术主导、车网互联
市场化:从政策驱动到多元盈利 政策全面赋能 2025年4月,国家发改委、能源局发布《关于加快推进虚拟电厂发展的指导意见》,首次明确虚拟电厂为“独立市场主体”,提出硬性目标:2027年全国调节能力≥2000万千瓦࿰…...
Git常用命令完全指南:从入门到精通
Git常用命令完全指南:从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...
MinIO Docker 部署:仅开放一个端口
MinIO Docker 部署:仅开放一个端口 在实际的服务器部署中,出于安全和管理的考虑,我们可能只能开放一个端口。MinIO 是一个高性能的对象存储服务,支持 Docker 部署,但默认情况下它需要两个端口:一个是 API 端口(用于存储和访问数据),另一个是控制台端口(用于管理界面…...