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 …...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...

黑马Mybatis
Mybatis 表现层:页面展示 业务层:逻辑处理 持久层:持久数据化保存 在这里插入图片描述 Mybatis快速入门 
Docker 运行 Kafka 带 SASL 认证教程
Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明:server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...

【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例
文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…...

P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...
WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)
一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解,适合用作学习或写简历项目背景说明。 🧠 一、概念简介:Solidity 合约开发 Solidity 是一种专门为 以太坊(Ethereum)平台编写智能合约的高级编…...

微信小程序云开发平台MySQL的连接方式
注:微信小程序云开发平台指的是腾讯云开发 先给结论:微信小程序云开发平台的MySQL,无法通过获取数据库连接信息的方式进行连接,连接只能通过云开发的SDK连接,具体要参考官方文档: 为什么? 因为…...
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南 在数字化营销时代,邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天,我们将深入解析邮件打开率、网站可用性、页面参与时…...