彻底搞懂Java多态

彻底搞懂Java多态很多初学者在自学 Java 时候都卡在了 Java 多态 本教程从实际案例出发阐述 Java 多态现象及 Java 多态的原理 通过案例理解多态的现象需求描述多态是类在继承关系下的一种形态 下边先通过一个需求展示下多态的现象 攀博课堂是一个在线教育学习平台 有一个具体的功能需求 当学员登录后系统需要根据学员的类型获取他在攀博课堂的服务权限 比如 对于普通学生他可以自学 Java 课程 下载资源 在线问答交流 对于 Vip 学员还可以额外有专属老师指导 专属交流群等 Vip 服务 如何使用面向对象的编程思想实现这一功能需求

很多初学者在自学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{ //用户id private 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", "攀博", "",1); //Vip学生 PbStudent student = new PbStudent("101", "攀博", "",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

代码如下:

课程类:

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{ //用户id private 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", "攀博", ""); //获取学生服务内容 PbMain.getStudentService(pbStudentGenaral); //创建vip学生类 PbStudentVip pbStudentVip = new PbStudentVip("101", "攀博", "", 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", "攀博", ""); //获取学生服务内容 PbMain.getStudentService(pbStudentGenaral); //创建vip学生类 PbStudentVip pbStudentVip = new PbStudentVip("101", "攀博", "", LocalDate.now().plusDays(365)); //获取vip学生服务内容 PbMain.getStudentService(pbStudentVip); } } 

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

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

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

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

多态的原理-向上转型

向上转型

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

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

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

向下转型

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

 PbStudent studentVip = new PbStudentVip("101", "攀博", "", 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", "攀博", "", 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是编译通过的。

多态的原理-动态绑定

动态绑定

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

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

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

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

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

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

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

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

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

静态绑定

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

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

实现多态过程(总结)

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

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

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

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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/233293.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • gradle教程_Gradle教程

    gradle教程_Gradle教程gradle教程WelcometoGradleTutorial.Inmyearlierposts,welookedintoWhatisGradleandGradleEclipsePlugin.欢迎使用Gradle教程。在我之前的文章中,我们研究了什么是Gradle和GradleEclipse插件。Gradle教程(GradleTutorial)…

    2022年6月28日
    26
  • Spring获取request对象的几种方式[通俗易懂]

    Spring获取request对象的几种方式[通俗易懂]参考文章:在SpringMVC中获取request对象的几种方式Springboot获取request和response使用Springboot,我们很多时候直接使用@PathVariable、@RequestParam、@Param来获取参数,但是偶尔还是要用到request和response,怎么获取呢?也很方便,有三种方式可以获取,任选其一就行。1、通过静态方法…

    2022年5月18日
    50
  • 一键生成惊雷等喊麦歌词

    一键生成惊雷等喊麦歌词思路写一下 1 给一长篇的小说 利用正则表达式将小说分为无数的字符串 2 将从后到前字符串切割 最长为 n 否则一句话太长 3 并且将每一个字符串的末尾音节提取出来 普通的韵脚音的话放在一个字符数组里面 3 增加一个 hashmap 表 添加平时用的韵脚 4 将符合 hashmap 的韵脚放在字典里 5 规定歌词的格式 比如 随机从字典取出字

    2025年6月5日
    0
  • 手机扫码登录实现思路是什么_扫码登录wifi如何实现

    手机扫码登录实现思路是什么_扫码登录wifi如何实现手机扫码登录实现思路,从业务场景逐个解决问题,引出实现方案

    2025年8月1日
    1
  • python和java哪个好学-Python和Java对比,全面解读哪个语言最赚钱,前景最好?

    python和java哪个好学-Python和Java对比,全面解读哪个语言最赚钱,前景最好?都知道现在最火爆的是人工智能、大数据。而人工智能和大数据主要用的语言就是Java和Python。今天我们就来分析一下,当前java和python,哪个就业前景更好,薪资更高?该学哪一个?一、语言历史Python:生而为简Python是一门拥有简洁语法的高级编程语言。一个名为GuidovanRossum的荷兰大佬在1991年设计了它。Rossum设计这门语言的初衷,就是为了让代码读起来更轻松,并且…

    2022年7月8日
    18
  • 什么是用户态和内核态的区别_内核态和用户态的概念

    什么是用户态和内核态的区别_内核态和用户态的概念什么是用户态和内核态从图上我们可以看出来通过系统调用将Linux整个体系分为用户态和内核态(或者说内核空间和用户空间)。那内核态到底是什么呢?其实从本质上说就是我们所说的内核,它是一种特殊的软件程序,特殊在哪儿呢?控制计算机的硬件资源,例如协调CPU资源,分配内存资源,并且提供稳定的环境供应用程序运行。用户态就是提供应用程序运行的空间,为了使应用程序访问到内核管理的资源例如CPU,内存,I/O。内核必须提供一组通用的访问接口,这些接口就叫系统调用。为什么要区分内核态和用户态往往我们的系统的资源是

    2022年9月16日
    0

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号