彻底搞懂Java多态

article/2025/9/12 22:53:01

很多初学者在自学Java时候都卡在了Java多态,本教程从实际案例出发阐述Java多态现象及Java多态的原理。

通过案例理解多态的现象

需求描述

多态是类在继承关系下的一种形态,下边先通过一个需求展示下多态的现象。

攀博课堂是一个在线教育学习平台,有一个具体的功能需求:当学员登录后系统需要根据学员的类型获取他在攀博课堂的服务权限,比如:对于普通学生他可以自学Java课程、下载资源、在线问答交流,对于Vip学员还可以额外有专属老师指导、专属交流群等 Vip服务,如何使用面向对象的编程思想实现这一功能需求。

每种学生类型的服务内容如下:
在这里插入图片描述

解决方案1

根据不同的学生类型获取不同的服务内容,首先想到的就是分支结构,在学生类中设置一个成员变量记录学生类型,在获取服务内容的方法中判断学生类型,然后根据不同的学生类型获取不同的服务内容。
在这里插入图片描述
代码如下:

课程类:

package com.pbteach.javase.oop.polymorphism.v1;/*** 	课程* @author 攀博课堂(www.pbteach.com)**/
public class PbCourse {//课程标识private long id;//课程名称private String courseName;//课程价格private float price;public PbCourse() {}public PbCourse(long id, String courseName, float price) {this.id = id;this.courseName = courseName;this.price = price;}public long getId() {return id;}public void setId(long id) {this.id = id;}public String getCourseName() {return courseName;}public void setCourseName(String courseName) {this.courseName = courseName;}public float getPrice() {return price;}public void setPrice(float price) {this.price = price;}}

学生类:

package com.pbteach.javase.oop.polymorphism.v1;/***	 攀博课堂学生类* @author 攀博课堂(www.pbteach.com)**/
public class PbStudent{//用户idprivate String id;//昵称private String nickname;//邮箱private String email;//头像private String pic;//密码private String password;//选课列表private PbCourse[] selections;//用户分组private int group;//获取服务public void getService() {if(group == 1) {//普通学生System.out.println("攀博课堂自学Java课程");System.out.println("资源下载");System.out.println("在线问答交流");}else if(group == 2) {//vip学生System.out.println("攀博课堂自学Java课程");System.out.println("资源下载");System.out.println("在线问答交流");System.out.println("攀博课堂Vip专属老师指导");System.out.println("攀博课堂Vip专属交流群");}//..有其它类型的学生向后加}public PbStudent() {System.out.println("PbStudent="+this);}//提供三个基本数据的构造方法public PbStudent(String id,String nickname,String email,int group) {this.id = id;this.nickname = nickname;this.email = email;this.group = group;}public void selectCourse(PbCourse course) {System.out.println("学生选课"+course.getCourseName());}public String getId() {return id;}public void setId(String id) {this.id = id;}public String getNickname() {return nickname;}public void setNickname(String nickname) {this.nickname = nickname;}public String getEmail() {return email;}public void setEmail(String email) {this.email = email;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public PbCourse[] getSelections() {return selections;}public void setSelections(PbCourse[] selections) {this.selections = selections;}public String getPic() {return pic;}public void setPic(String pic) {this.pic = pic;}}

测试类:

package com.pbteach.javase.oop.polymorphism.v1;public class PbMain {public static void main(String[] args) {//普通学生
//		PbStudent student = new PbStudent("101", "攀博", "pbteach@126.com",1);//Vip学生PbStudent student = new PbStudent("101", "攀博", "pbteach@126.com",2);student.getService();}}

本实现的核心代码如下,使用分支结构的问题是如果再增加学生类型需要修改代码,程序的可维护性差。

	//获取服务public void getService() {if(group == 1) {//普通学生System.out.println("攀博课堂自学Java课程");System.out.println("资源下载");System.out.println("在线问答交流");}else if(group == 2) {//vip学生System.out.println("攀博课堂自学Java课程");System.out.println("资源下载");System.out.println("在线问答交流");System.out.println("攀博课堂Vip专属老师指导");System.out.println("攀博课堂Vip专属交流群");}//..有其它类型的学生向后加}

解决方案2

本方案是使用继承关系实现,根据需求描述中的图示,见下图,可以定义一个学生基础类,定义普通学生类和Vip学生类,然后分别在两个子类中增加获取服务内容的方法getService(),如下图:
在这里插入图片描述
在PbMain类中分别针对不同的学生子类定义重载方法getStudentService()。

代码如下:

课程类:

package com.pbteach.javase.oop.polymorphism.v2;/*** 	课程类* @author 攀博课堂(www.pbteach.com)**/
public class PbCourse {//课程标识private long id;//课程名称private String courseName;//课程价格private float price;public PbCourse() {}public PbCourse(long id, String courseName, float price) {this.id = id;this.courseName = courseName;this.price = price;}public long getId() {return id;}public void setId(long id) {this.id = id;}public String getCourseName() {return courseName;}public void setCourseName(String courseName) {this.courseName = courseName;}public float getPrice() {return price;}public void setPrice(float price) {this.price = price;}}

学生基础类:

package com.pbteach.javase.oop.polymorphism.v2;/***	 攀博课堂学生基础类* @author 攀博课堂(www.pbteach.com)**/
public class PbStudent{//用户idprivate String id;//昵称private String nickname;//邮箱private String email;//头像private String pic;//密码private String password;//选课列表private PbCourse[] selections;//获取服务public void getService() {}public PbStudent() {System.out.println("PbStudent="+this);}//提供三个基本数据的构造方法public PbStudent(String id,String nickname,String email) {this.id = id;this.nickname = nickname;this.email = email;}public void selectCourse(PbCourse course) {System.out.println("学生选课"+course.getCourseName());}public String getId() {return id;}public void setId(String id) {this.id = id;}public String getNickname() {return nickname;}public void setNickname(String nickname) {this.nickname = nickname;}public String getEmail() {return email;}public void setEmail(String email) {this.email = email;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public PbCourse[] getSelections() {return selections;}public void setSelections(PbCourse[] selections) {this.selections = selections;}public String getPic() {return pic;}public void setPic(String pic) {this.pic = pic;}}

普通学生类:

package com.pbteach.javase.oop.polymorphism.v2;/***	 攀博课堂普通学生类* @author 攀博课堂(www.pbteach.com)**/
public class PbStudentGeneral extends PbStudent{public PbStudentGenaral(String id,String nickname,String email) {super(id, nickname, email);}//获取服务public void getService() {System.out.println("攀博课堂自学Java课程");System.out.println("资源下载");System.out.println("在线问答交流");}
}

Vip学生类:

package com.pbteach.javase.oop.polymorphism.v2;import java.time.LocalDate;/*** 	攀博课堂Vip学生类* @author 攀博课堂(www.pbteach.com)**/
public class PbStudentVip extends PbStudent {// vip服务截止时间private LocalDate vipDeadline;// vip专属指导老师private String pbteacher;// vip专属群private String pbgroup;//获取服务public void getService() {System.out.println("攀博课堂自学Java课程");System.out.println("资源下载");System.out.println("在线问答交流");System.out.println("攀博课堂Vip专属老师指导");System.out.println("攀博课堂Vip专属交流群");}//校验有效性public boolean isValid() {//服务截至时间大于当前时间则账户有效if(vipDeadline.isAfter(LocalDate.now())) {return true;}return false;}//方法重写public void selectCourse(PbCourse course) {//校验vip有效性if(!isValid()) {return ;}//调用父类的选课方法super.selectCourse(course);}//提供四个基本数据类型的构造方法public PbStudentVip(String id,String nickname,String email,LocalDate vipDeadline) {//调用父类的构造方法进行初始化super(id, nickname, email);this.vipDeadline = vipDeadline;}public LocalDate getVipDeadline() {return vipDeadline;}public void setVipDeadline(LocalDate vipDeadline) {this.vipDeadline = vipDeadline;}public String getPbteacher() {return pbteacher;}public void setPbteacher(String pbteacher) {this.pbteacher = pbteacher;}public String getPbgroup() {return pbgroup;}public void setPbgroup(String pbgroup) {this.pbgroup = pbgroup;}}

测试类:

package com.pbteach.javase.oop.polymorphism.v2;import java.time.LocalDate;public class PbMainOld {//获取普通学生服务内容public static void getStudentService(PbStudentGenaral studentGenaral) {studentGenaral.getService();}//获取Vip学生服务内容public static void getStudentService(PbStudentVip studentVip) {studentVip.getService();}public static void main(String[] args) {//创建普通学生类PbStudentGenaral pbStudentGenaral = new PbStudentGenaral("101", "攀博", "pbteach@126.com");//获取学生服务内容PbMain.getStudentService(pbStudentGenaral);//创建vip学生类PbStudentVip pbStudentVip = new PbStudentVip("101", "攀博", "pbteach@126.com", LocalDate.now().plusDays(365));//获取vip学生服务内容PbMain.getStudentService(pbStudentVip);}}

使用继承关系就没有分支结构的繁琐,每种学生类型各自己实现自己的方法,如果增加学生类型只需要增加相应的类即可。

问题依然存在,在PbMain主控类中,针对每个学生类型都需要定义一个getStudentService重载方法,后期如果增加其它学生类型还需要增加相应的getStudentService。

总结多态的现象

解决方案2相比解决方案1更加整洁,没有if分支判断的繁琐,但对代码的可维护性还很差,如果使用多态即可解决,我们更改PbMain中的方法,即可实现我们的目标:

package com.pbteach.javase.oop.polymorphism.v2;import java.time.LocalDate;public class PbMain {//获取学生服务内容public static void getStudentService(PbStudent student) {student.getService();}public static void main(String[] args) {//创建普通学生类PbStudentGenaral pbStudentGenaral = new PbStudentGenaral("101", "攀博", "pbteach@126.com");//获取学生服务内容PbMain.getStudentService(pbStudentGenaral);//创建vip学生类PbStudentVip pbStudentVip = new PbStudentVip("101", "攀博", "pbteach@126.com", LocalDate.now().plusDays(365));//获取vip学生服务内容PbMain.getStudentService(pbStudentVip);}}

上边的代码即使用多态实现了需求,使用统一方法查询学生的服务类型:

//获取学生服务内容
public static void getStudentService(PbStudent student) {student.getService();
}

此方法接收PbStudent的子类型,当传入PbStudentGenaral普通学生对象则查询普通学生对象的服务 内容,当传入PbStudentVip学生对象则查询 Vip学生的服务内容,注意调用的就是同一个student.getService();方法。

多态就是同一方法对不同的子类对象所产生的不同的形态。

多态的原理-向上转型

向上转型

多态是如何实现向一个方法传入不同的类型得到不同的结果?这里是因为出现了叫上转型,即将子类对象地址赋值给父类引用,void getStudentService(PbStudent student)方法接收了不同的子类对象地址。
在这里插入图片描述
既然可以可上转型 那么多态的代码可以写成如下的方式:

请尝试找到下边代码的不同点:

原来:
//创建普通学生类
PbStudentGenaral pbStudentGenaral = new PbStudentGenaral("101", "攀博", "pbteach@126.com");
//创建vip学生类
PbStudentVip pbStudentVip = new PbStudentVip("101", "攀博", "pbteach@126.com", LocalDate.now().plusDays(365));更改后:
//创建普通学生类
PbStudent studentGenaral = new PbStudentGenaral("101", "攀博", "pbteach@126.com");
//创建Vip学生类
PbStudent studentVip = new PbStudentVip("101", "攀博", "pbteach@126.com", LocalDate.now().plusDays(365));

更改后的代码验证了向上转型。

向下转型

有向上转型自然有向下转型,如果想调用子类特有的方法必须向下转型,如下:

在PbStudentVip类中isValid()方法是它特有的,如果通过父类调用子类特有的方法是编译不通过的,因为父类没有子类特有的方法。
在这里插入图片描述
如果要调用子类的方法必须向下转型,即由父类型转为子类型。

		PbStudent studentVip = new PbStudentVip("101", "攀博", "pbteach@126.com", LocalDate.now().plusDays(365));//将studentVip转为PbStudentVip类型PbStudentVip pbStudentVip = (PbStudentVip) studentVip;//调用vip学生类特有的方法pbStudentVip.isValid();

(PbStudentVip) studentVip;表示将studentVip的类型转为PbStudentVip类型。

注意:向下转型是非常危险的,要谨慎使用,如果studentVip并不是PbStudentVip类型,但是编译时并不报错,在调用isValid()方法时则报错。所以必须确切知道 studentVip可以转为PbStudentVip类型才可以用向下转型。

instanceof

向下转型可以用instanceof进行判断,如下代码:

		PbStudent studentVip = new PbStudentVip("101", "攀博", "pbteach@126.com", LocalDate.now().plusDays(365));if (studentVip instanceof PbStudentVip) {//将studentVip转为PbStudentVip类型PbStudentVip pbStudentVip = (PbStudentVip) studentVip;//调用vip学生类特有的方法pbStudentVip.isValid();}

instanceof 是一个双目运算符,用来判断对象是否为某个类型,studentVip instanceof PbStudentVip表示判断studentVip 指向的对象是否为PbStudentVip, 因为Java允许向上转型,所以父类引用变量可能会指向它的子类对象,所以studentVip instanceof PbStudentVip是编译通过的。

如果instanceof 两端是不兼容的变量和类型则编译不通过,因为PbStudent类型的变量所指向的对象不可能是PbCourse类型的,见下图:
在这里插入图片描述

多态的原理-动态绑定

动态绑定

使用向上转型将子类对象地址赋值给父类引用,如下代码:

//创建普通学生类
PbStudent studentGenaral = new PbStudentGenaral("101", "攀博", "pbteach@126.com");
studentGenaral.getService();
//创建Vip学生类
PbStudent studentVip = new PbStudentVip("101", "攀博", "pbteach@126.com", LocalDate.now().plusDays(365));
studentVip.getService();

上边的代码显示不管是构造PbStudentGenaral类型的对象还是PbStudentVip类型的对象,都是调用的父类PbStudent的getService();,却可以产生不同的输出结果。

首先查询父类PbStudent的getService();的方法:

//获取服务
public void getService() {
}

大吃一惊,方法竟然是空的!这是什么原因呢?

其实,在运行时调用的方法并不是父类的这个空方法,而是具体的子类对象的方法,当引用指向的是普通学生类对象则调用普通学生类的方法,当指向的是Vip学生类则调用Vip学生类的方法。

在这里插入图片描述
这个过程是在程序运行时根据对象所属类型找到具体的方法进行调用,这叫动态绑定,即程序运行期间根据对象类型进行绑定。
空方法有什么用?

空方法是编译器要求存在的,虽然编译器不知道后期绑定哪个类的方法,但编译器要求必须得有一个父类PbStudent的中方法与代码一致,否则编译不通过。

我们可以尝试屏蔽PbStudent类中的getService() 方法,编译器报错:getService() 在PbStudent中没有定义。
在这里插入图片描述

静态绑定

动态 绑定是在运行时才确定的类型,静态绑定则是在编译时就确定类型。

子类无法重写的方法就是静态绑定方法,比如static方法、final方法、private方法。

实现多态过程(总结)

通过向上转型及动态绑定的分析,实现多态的步骤如下:

1、实现继承关系,并在子类实现方法重写。

2、构造子类对象并赋值给父类引用变量。(向上转型)

3、调用父类的的方法,根据父类引用变量指向的对象找到具体子类的方法进行调用。(动态绑定)


http://chatgpt.dhexx.cn/article/qylyuMvo.shtml

相关文章

java多态实现原理

众所周知,多态是面向对象编程语言的重要特性,它允许基类的指针或引用指向派生类的对象,而在具体访问时实现方法的动态绑定。C 和 Java 作为当前最为流行的两种面向对象编程语言,其内部对于多态的支持到底是如何实现的呢&#xff0…

新手小白学JAVA 面向对象之多态

多态 1. 概念 多态是面向对象程序设计(OOP)的一个重要特征,指同一个实体同时具有多种形式,即同一个对象,在不同时刻,代表的对象不一样,指的是对象的多种形态。 可以把不同的子类对象都当作父…

html表单实例:用户反馈表单页面代码

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>demo</title> </head> <body><h1 align"center">用户反馈表单</h1><form method"post"><…

html 下拉多选框代码,js实现下拉复选框效果(代码实例)

本章给大家带来用js实现下拉复选框效果(代码实例),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。 先看看效果: 下面我们看看代码: HTML代码: HTMLCSSJavaScriptjQueryPHPMySQLJavaC#C++Pyhtoncss代码:div {display: inline-block; } select {min-width…

HTML实例--制作表单

运用表格和表单基础知识简单制作一个表单 表单制作使用表格来对表单进行排版美化 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge">…

Html5 Canvas绘图实例

前些年的时候&#xff0c;突然对Canvas感兴趣&#xff0c;利用空闲时间做一些Canvas小例子进行练习&#xff0c;仅供学习分享使用。如有不足之处&#xff0c;还请指正。 什么是 Canvas&#xff1f; HTML5 的 canvas 元素使用 JavaScript 在网页上绘制图像。画布是一个矩形区域…

用html做个随机点名系统代码,html座位表随机点名的实例代码

这篇文章详解html座位表随机点名的实例代码点名 td { width: 9.09%; height: 50px; text-align: center; } .tdBg { background-color: pink; } var timer null; // 这是一个函数&#xff0c;表示一个功能 function start(){ timer setInterval(function(){ // alert("要…

在html中如何写日期的代码,日期html代码

日期 时间 星期的html代码是什么代码 创建静态方法findDate,返回List类型。 声明一个List list集合,向List集合存储英文星期。 调用findDate静态方法,并打印List集合存储结果。 CSS布局HTML小编今天和大家分享一个显示当前系统日期的HTML代码 显示的格式为“某年某月某日”,…

html向上移动图片代码,图片随网页上下移动的代码实例

我们以腾讯QQ网页在线客服为例,大家将代码拷到DW中,用心体会。图片随网页上下移动的代码实例 function picsize(obj,MaxWidth){img=new Image(); img.src="/obj.src"; if (img.width>MaxWidth) {return MaxWidth; } else {return img.width; } } function Close…

html5 简单实例源代码

实例教程&#xff1a;http://www.w3school.com.cn/jquery/ 源代码下载&#xff1a; http://download.csdn.net/detail/wyx100/9827067 html5文件布局结构 html5文件布局结构html5语言标记 浏览器执行效果 html5文件源代码 源代码下载&#xff1a; http://download.csdn.net/de…

html实例,实现表单

1.使用HTML完成下列功能 <!doctype html> <html><head><!-- <meta charset"GBK">--></head><body><table width"60%" border"3" align"center" bgcolor"#F0F8FF" borderColor…

html导航栏纵向代码,html横向导航栏怎么做?横向导航条代码实例

有不少小伙伴在刚学习 html 的时候都会遇到这样一个问题&#xff1a;html 横向导航栏怎么做&#xff1f;今天W3Cschool小编就为大家分享一下简单的横向导航条代码&#xff0c;相信会对大家有所帮助。 html 横向导航栏一般用两种方法来制作&#xff1a;第一种&#xff0c;我们使…

html如何插入下拉菜單,html下拉菜单怎么做?html下拉菜单的代码实例介绍

本篇文章主要的介绍了关于HTML select标签下拉菜单的做法实例&#xff0c;还有一个html的一些网站的下拉菜单的用法都放在了文章中&#xff0c;下面就让我们一起来看看这篇文章吧 首先我们要知道html下拉菜单的代码是什么&#xff1f; 很明显是select元素可创建单选或多选菜单。…

HTML代码示例和介绍

HTML基本的格式 <!DOCTYPE html> <!-- 声明文档。定义html --> <html lang"en"> <!-- 元素是页面的根元素 --> <head> <!-- 元素包含文档的元数据 --><meta charset"UTF-8"&g…

STM32仿真器下载程序出现SWD/JTAG Communication Failure的解决方法

一、解决办法&#xff1a;将STM32开发板断电&#xff0c;将板子上的BOOT0用短路帽接入3.3V高电平&#xff0c;重新插入仿真器&#xff0c;下载程序到开发板。不出意外可见程序烧录成功&#xff0c;此时将BOOT0接回低电平&#xff0c;后续烧录程序便不会出现SWD/JTAG Communicat…

keil无法识别JTAG仿真器解决办法

一、操作步骤 1、操作环境&#xff1a; 开发板&#xff1a;野火STM32H743XI 电脑系统版本&#xff1a;Windows 10 专业版 使用笔记本调试 JTAG&#xff1a;Fire-Debugger 野火 高速版DAP编程器 2、操作步骤&#xff1a; 将JTAG连接在STM32调试接口和电脑USB接口上&#xff0c…

JTAG调试原理

转自&#xff1a;https://blog.csdn.net/sinat_24088685/article/details/50980501 1.介绍 JTAG&#xff08;Joint Test Action Group&#xff0c;联合测试行动小组&#xff09; 是一种 国际标准测试 协议&#xff0c;主要用于 芯片内部测试 。现在多数的高级器件都支…

MCU模拟JTAG接口对LATTICE CPLD FPGA 进行在线编程加载

完整版请点击 https://hifpga.com/问题/719 索取源码&#xff0c;向博主本人提问FPGA相关问题 作者&#xff1a;Rock.Ding&#xff08;莱迪思半导体公司&#xff09;关键字&#xff1a;MCU, JTAG, 在线编程, CPLD。 前言 CPLD(Complex Programmable Logic Device)复杂可编程…

JTAG+SWD在Keil5中进行仿真

JTAGSWD在Keil5中进行仿真 上一章说了STM32的烧录问题&#xff0c;主要有slink、TTL-usb的方法&#xff0c;通过相应的烧录软件&#xff0c;进行一个下载烧录的过程&#xff0c;用到的模式也主要是SWD的模式&#xff0c;毕竟只有四根线比较方便。 这篇主要是仿真测试&#xff…

【开发工具】【JTAG】JTAG调试原理【二】

相关链接&#xff1a; JTAG基础 JTAG调试原理 JTAG调试实例 模拟系统崩溃&#xff0c;使用JTAG调试找到崩溃点 JTAG调试原理 两个重要概念&#xff1a;边界扫描和TAP 边界扫描 JTAG如何用于芯片测试呢&#xff1f; 其中用到的最主要部件就是边界扫描链。 边界扫描&…