【JAVASE】多态

⭐ 作者:小胡_不糊涂
🌱 作者主页:小胡_不糊涂的个人主页
📀 收录专栏:浅谈Java
💖 持续更文,关注博主少走弯路,谢谢大家支持 💖

多态

  • 1. 概念
  • 2. 实现条件
  • 3. 重写
  • 4. 向上转型和向下转型
    • 4.1 向上转型
    • 4.2 向下转型
  • 5. 多态的优点
  • 6. 避免在构造方法中调用重写的方法

在这里插入图片描述

1. 概念

多态:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。
好比同一个人对待不同的人表现出的形态是不一样的。

比如:彩色打印机和黑白打印机都是打印机,但是他们的打印效果是不同的。

🍩同一件事情,发生在不同对象身上,就会产生不同的结果

2. 实现条件

在java中要实现多态,必须要满足如下几个条件,缺一不可:

  • 必须在继承体系下
  • 子类必须要对父类中方法进行重写
  • 通过父类的引用调用重写的方法

多态体现: 在代码运行时,当传递不同类对象时,会调用对应类中的方法。

例如:

//定义一个动物类
class Animal {String name;int age;public Animal(String name, int age){this.name = name;this.age = age;}public void eat(){System.out.println(name + "吃饭");}
}//定义一个狗类
class Dog extends Animal {public Dog(String name, int age){super(name, age);}/*public Dog() {super();}*/public void bark() {System.out.println(this.name+ "汪汪叫");}@Override//重写了Animal中的eat方法public void eat(){System.out.println(name+"吃骨头~~~");}
}//定义一个猫类
class Cat extends Animal{public Cat(String name, int age){super(name, age);}public void miaomiao() {System.out.println(this.name+"咪咪叫!");}/*@Overridepublic void eat(){System.out.println(name+"吃鱼~~~");}*/
}//-------------------------------------public class Test {// 编译器在编译代码时,并不知道要调用Dog 还是 Cat 中eat的方法// 等程序运行起来后,形参a引用的具体对象确定后,才知道调用那个方法// 注意:此处的形参类型必须时父类类型才可以public static void eat(Animal a){a.eat();}public static void main(String[] args) {Cat cat = new Cat("小花",2);Dog dog = new Dog("Peter", 1);eat(cat);//此时Cat类没有eat()方法,会引用父类的eat()eat(dog);dog.bark();//直接引用bark()cat.miaomiao();}
}

🍤 运行结果:

在这里插入图片描述

在上述代码中,分割线上方的代码是类的实现者编写的,分割线下方的代码是类的调用者编写的。

当类的调用者在编写 eat 这个方法的时候,参数类型为 Animal (父类),此时在该方法内部并不知道,也不关注当前的a 引用指向的是哪个类型(哪个子类)的实例,此时 a这个引用调用 eat方法可能会有多种不同的表现(和 a 引用的实例相关),这种行为就称为多态。

3. 重写

重写(override):也称为覆盖。

重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写,返回值和形参都不能改变。即外壳不变,核心重写
重写的好处在于子类可以根据需要,定义特定于自己的行为。也就是说子类能够根据需要实现父类的方法。

方法重写规则:

  • 子类在重写父类的方法时,一般必须与父类方法原型一致: 返回值类型 方法名 (参数列表) 要完全一致。
  • 被重写的方法返回值类型可以不同,但是必须是具有父子关系的。
  • 访问权限不能比父类中被重写的方法的访问权限更低。
    例如:如果父类方法被public修饰,则子类中重写该方法就不能声明为 protected
  • 父类被static、private修饰的方法、构造方法都不能被重写。
  • 重写的方法,可以使用 @Override 注解来显式指定,有了这个注解能帮我们进行一些合法性校验。
    例如不小心将方法名字拼写错了 (比如写成 aet),那么此时编译器就会发现父类中没有 aet 方法,就会编译报错,提示无法构成重写。

重写和重载的区别:

区别点重写(override)重载(override)
参数列表一定能修改必须修改
返回类型一定不能修改【除非可以构成父子类关系】可以修改
访问限定符一定不能做更严格的限制(可以降低限制)可以修改

🍩方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现

在这里插入图片描述

重写的设计原则:

对于已经投入使用的类,尽量不要进行修改。最好的方式是:重新定义一个新的类,来重复利用其中共性的内容,并且添加或者改动新的内容。

静态绑定: 也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。典型代表函数重载。

动态绑定: 也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用那个类的方法

4. 向上转型和向下转型

4.1 向上转型

向上转型:实际就是创建一个子类对象,将其当成父类对象来使用。

语法格式:父类类型 对象名 = new 子类类型()

例如:

Animal animal = new Cat("小花",2);
//animal是父类类型,但可以引用一个子类对象,因为是从小范围向大范围的转换

在这里插入图片描述

使用场景:

  • 直接赋值
  • 方法传参
  • 方法返回
public class Test1 {// 2. 方法传参:形参为父类型引用,可以接收任意子类的对象public static void eatFood(Animal a){a.eat();}// 3. 作返回值:返回任意子类对象public static Animal buyAnimal(String var){if("狗".equals(var) ){return new Dog("狗狗",1);}else if("猫" .equals(var)){return new Cat("猫猫", 1);}else{return null;}}public static void main(String[] args) {Animal cat = new Cat("小花",2); // 1. 直接赋值:子类对象赋值给父类对象Dog dog = new Dog("Peter", 1);eatFood(cat);eatFood(dog);Animal animal = buyAnimal("狗");animal.eat();animal = buyAnimal("猫");animal.eat();}
}

向上转型的优点:让代码实现更简单灵活。
向上转型的缺陷:不能调用到子类特有的方法。

4.2 向下转型

将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法,但有时候可能需要调用子类特有的方法,此时:将父类引用再还原为子类对象即可,即向下转换。

在这里插入图片描述

public class Test1 {public static void main(String[] args) {Cat cat = new Cat("小花",2);Dog dog = new Dog("Peter", 1);// 向上转型Animal animal = cat;animal.eat();animal = dog;animal.eat();animal.bark();// 编译失败,编译时编译器将animal当成Animal对象处理,而Animal类中没有bark方法,因此编译失败// 向上转型cat = (Cat)animal;cat.miaomiao();// 程序可以通过编程,但运行时抛出异常---因为:animal实际指向的是狗,现在要强制还原为猫,无法正常还原dog = (Dog)animal;// animal本来指向的就是狗,因此将animal还原为狗也是安全的dog.bark();}
}

向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。Java中为了提高向下转型的安全性,引入了 instanceof ,如果该表达式为true,则可以安全转换:

public class Test1 {public static void main(String[] args) {Cat cat = new Cat("小花",2);Dog dog = new Dog("Peter", 1);// 向上转型Animal animal = cat;animal.eat();animal = dog;animal.eat();//类型比较运算符:instanceofif(animal instanceof Cat){cat = (Cat)animal;cat.miaomiao();}if(animal instanceof Dog){dog = (Dog)animal;dog.bark();}}
}

🍤 运行结果:

在这里插入图片描述

5. 多态的优点

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

圈复杂度是一种描述一段代码复杂程度的方式。
一段代码如果平铺直叙,那么就比较简单容易理解,而如果有很多的条件分支或者循环语句,就认为理解起来更复杂。
因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数,这个个数就称为 “圈复杂度”。
如果一个方法的圈复杂度太高, 就需要考虑重构。

实例:打印多个形状

public class drawShape {public static void main(String[] args) {//实例化Rect rect = new Rect();Cycle cycle = new Cycle();Flower flower = new Flower();String[] shapes = {"cycle", "rect", "cycle", "rect", "flower"};//遍历shapes数组for (String shape : shapes) {if (shape.equals("cycle")) {cycle.draw();//画●} else if (shape.equals("rect")) {rect.draw();//画♦} else if (shape.equals("flower")) {flower.draw();//画❀}}}
}class Shape {//属性....public void draw() {System.out.println("画图形!");}
}
class Rect extends Shape{@Overridepublic void draw() {System.out.println("♦");}
}
class Cycle extends Shape{@Overridepublic void draw() {System.out.println("●");}
}
class Flower extends Shape {@Overridepublic void draw() {System.out.println("❀");}
}

🍤 运行结果:

在这里插入图片描述

上面的代码使用 if-else 分支语句太过复杂,我们可以使用多态来简化代码:

public class drawShape {public static void main(String[] args) {// 创建了一个 Shape 对象的数组Shape[] shapes = {new Cycle(), new Rect(), new Cycle(),new Rect(), new Flower()};for (Shape shape : shapes) {shape.draw();//根据shapes所指的对象,引用不同类中的draw}}
}
  1. 可扩展能力更强

如果要新增一种新的形状,使用多态的方式代码改动成本也比较低。

//新增一个形状
class Triangle extends Shape {@Overridepublic void draw() {System.out.println("△");}
}

对于类的调用者来说,只要创建一个新类的实例就可以了,改动成本很低。
而对于不用多态的情况,就要把 main 中的 if - else 进行一定的修改,改动成本更高。

多态缺陷:代码的运行效率降低。

  1. 属性没有多态性
    当父类和子类都有同名属性的时候,通过父类引用,只能引用父类自己的成员属性
  2. 构造方法没有多态性

6. 避免在构造方法中调用重写的方法

下面是一段有坑的代码:
创建两个类,B 是父类,D 是子类。D 中重写 func 方法,并且在 B 的构造方法中调用 func

class B {public B() {func();}public void func() {System.out.println("B.func()");}
}class D extends B {private int num = 1;@Overridepublic void func() {System.out.println("D.func() " + num);}
}public class Test {public static void main(String[] args) {D d = new D();}
}

🍤 运行结果:

在这里插入图片描述
在构造 D 对象的同时,会调用 B 的构造方法,而B 的构造方法中调用了 func 方法,此时会触发动态绑定,会调用到 D 中的 func。此时 D 对象自身还没有构造, num 处在未初始化的状态值为 0。 如果具备多态性,num的值应该是1。
所以在构造函数内,尽量避免使用实例方法,除了 final 和 private 方法。

结论:

用尽量简单的方式使对象进入可工作状态,尽量不要在构造器中调用方法(如果这个方法被子类重写,就会触发动态绑定,但是此时子类对象还没构造完成),可能会出现一些隐藏的但是又极难发现的问题。


在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

wordpress 学习贴

安装问题 我的使用环境为docker环境,php、nginx、mysql分别处于3个容器中, 提示异常,打开debug模式,会发现 No such file or directory Warning: mysqli_real_connect(): (HY000/2002): No such file or directory 这个其实问题其…

【C++】开源:sqlite3数据库配置使用

😏★,:.☆( ̄▽ ̄)/$:.★ 😏 这篇文章主要介绍sqlite3数据库配置使用。 无专精则不能成,无涉猎则不能通。——梁启超 欢迎来到我的博客,一起学习,共同进步。 喜欢的朋友可以关注一下,下…

【FAQ】如何隐藏网页H.265播放器EasyPlayer.js的实时录像按钮?

目前我们TSINGSEE青犀视频所有的视频监控平台,集成的都是EasyPlayer.js版播放器,它属于一款高效、精炼、稳定且免费的流媒体播放器,可支持多种流媒体协议播放,包括WebSocket-FLV、HTTP-FLV,HLS(m3u8&#x…

MySQL建表和增添改查

1.创建一个名为mydb的数据库 mysql> show database mydb; 查询 mysql> show database mydb; 2.创建一个学生信息表 mysql> create table mydb.student_informtion( -> student_id int UNSIGNED NOT NULL PRIMARY KEY, //非空(不允许为空&#xff0…

unity如何手动更改脚本执行顺序

在Unity中,脚本的执行顺序是由脚本的执行顺序属性决定的。默认情况下,Unity根据脚本在项目中的加载顺序来确定它们的执行顺序。然而,你可以手动更改脚本的执行顺序,以下是一种方法: 在Unity编辑器中,选择你…

PCIE上位机用什么工具?

可以了解一下神电测控出器的My FPGA开发套件,它可以用来开发FPGA板卡与上位机之间PCIE通信,而且就是用LabVIEW FPGA开发。它使用的是Xillybus PCIe IP核,神电将其封装成了在 LabVIEW FPGA 下的 PCIe CLIP 组件,可以方便的使用。而…

深度学习之tensorboard可视化工具

(1)什么是tensorboard tensorboard是TensorFlow 的一个可视化工具包,提供机器学习实验所需的可视化和工具,该工具的功能如下: 跟踪和可视化指标,例如损失和精度可视化模型图(操作和层)查看权重、偏差或其…

kafka部署

1.kafka安装部署 1.1 kafaka下载 https://archive.apache.org/dist/kafka/2.4.0/kafka_2.12-2.4.0.tgz Binary downloads是指预编译的软件包,可供直接下载和安装,无需手动编译。在计算机领域中,二进制下载通常指预构建的软件分发包,可以直接安装在系统上并使用 "2.…

MFC第二十七天 通过动态链表实现游戏角色动态增加、WM_ERASEBKGND背景刷新的原理、RegisterClass注册窗口与框架程序开发

文章目录 通过动态链表实现游戏角色动态增加CMemoryDC.hCFlashDlg.hCFlashDlg.cpp WM_ERASEBKGND背景刷新的原理RegisterClass注册窗口与框架程序开发CFrameRegister 通过动态链表实现游戏角色动态增加 CMemoryDC.h #pragma once#include "resource.h"/*内存DC类简介…

JavaWeb三大组件 —— Servlet

目录 servlet 注册servlet 父pom pom文件 1、通过注解注册 2、使用ServletRegistrationBean注册 API三生三世 第一生Servlet 第二生SpringMVC 今生SpringBoot servlet Servlet的作用: 接受请求参数、处理请求,响应结果,(就…

了解 spring MVC + 使用spring MVC - springboot

前言 本篇介绍什么是spring MVC ,如何使用spring MVC,了解如何连接客户端与后端,如何从前端获取各种参数;如有错误,请在评论区指正,让我们一起交流,共同进步! 文章目录 前言1. 什么…

Vue 全局事件总线 Event-Bus

全局事件总线 作用:可以在全局层面上,在任意组件之间相互传递数据。不再局限于父子组件传值,或多层嵌套传值等方式。 使用方式:完全与父子组件传值一致,使用 $on 监听事件,使用 $emit 触发事件&#xff0c…

el-tooltip设置文字溢出时展示否则不展示

改写el-tooltip使其支持文字溢出时展示否则不展示,而不是需要使用js设置单独控制 新建 src/utils/rewriteElTooltip.js (一模一样 cv就行) export default function rewriteElTooltip(el) {el.props {...el.props,overflow: Boolea…

kagNet:对常识推理的知识感知图网络 8.4+8.5

这里写目录标题 摘要介绍概述问题陈述推理流程 模式图基础概念识别模式图构造概念网通过寻找路径来匹配子图基于KG嵌入的路径修剪 知识感知图网络图卷积网络(GCN)关系路径编码分层注意机制 实验数据集和使用步骤比较方法KAGNET是实施细节性能比较和分析I…

eNSP 路由器启动时一直显示 # 号的解决办法

文章目录 1 问题截图2 解决办法2.1 办法一:排除防火墙原因导致 3 验证是否成功 1 问题截图 路由器命令行一直显示 # 号,如下图 2 解决办法 2.1 办法一:排除防火墙原因导致 排查是否因为系统防火墙原因导致。放行与 eNSP 和 virtualbox 相…

【计算机网络】socket编程

文章目录 1. 网络通信的理解2.进程PID可以取代端口号吗?3. 认识TCP协议4. 认识 UDP协议5. socket编程接口udp_server.hpp的代码解析socket——创建 socket 文件描述符Initserver——初始化1.创建套接字接口,打开网络文件bind——绑定的使用 2.给服务器指…

spring-boot webservice的例子

webservice发布服务 源码下载地址 spring-boot-webservice例子资源-CSDN文库 webservice cilent调用 源码下载地址 spring-boot-clintwebservice调用服务的例子资源-CSDN文库

VSCode---通过ctrl+鼠标滚动改变字体大小

打开设置然后在右边输editor.mouseWheelZoo勾选即可实现鼠标滚动改变字体大小 4.这种设置的字体大小是固定的

Plugin [id: ‘com.android.application‘, version: ‘7.xx‘, apply: false] was not found in any ...

Plugin [id: com.android.application, version: 7.3.0-alpha03, apply: false] was not found in any of the following sources: 问题原因: 如上解释,所配置gradle版本在本地配置的gradle仓库里面没找到。 解决办法: 1.弄清楚自己本地的…

JVM基础篇-方法区与运行时常量池

JVM基础篇-方法区与运行时常量池 方法区 Java 虚拟机有一个在所有 Java 虚拟机线程之间共享的方法区。方法区类似于传统语言的编译代码的存储区或者类似于操作系统进程中的“文本”段。它存储每个类的结构,例如运行时常量池、字段和方法数据,以及方法和…