【JavaSE】面向对象编程思想之多态(图文详解)

目录

1. 多态的概念

2. 多态实现条件

3. 重写

4. 向上转型和向下转型

4.1 向上转型

4.2 向下转型

5. 多态的优缺点

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


1. 多态的概念

多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。

 总的来说:同一件事情,发生在不同对象身上,就会产生不同的结果。

2. 多态实现条件

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

1. 必须在继承体系下

2. 子类必须要对父类中方法进行重写

3. 通过父类的引用调用重写的方法

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

下面是一个多态的示例:

class Animal {public void makeSound() {System.out.println("动物发出声音");}
}class Cat extends Animal {@Overridepublic void makeSound() {System.out.println("猫发出喵喵的声音");}
}class Dog extends Animal {@Overridepublic void makeSound() {System.out.println("狗发出汪汪的声音");}
}public class Main {public static void main(String[] args) {Animal animal1 = new Cat();Animal animal2 = new Dog();animal1.makeSound(); // 输出:猫发出喵喵的声音animal2.makeSound(); // 输出:狗发出汪汪的声音}
}

3. 重写

引入:

在阅读下面代码之前,建议先学习向上转型。

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 class Test {public static void main(String[] args) {Animal animal = new Dog("小狗",2);animal.eat();}
}

 对于上面的代码,我们可以轻松的看出程序将会打印的内容:

 但是,如果在Dog这个类中也有eat()这个方法,程序会输出什么样的结果呢:

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 void eat() {System.out.println(name + "吃骨头~~");}
}
public class Test {public static void main(String[] args) {Animal animal = new Dog("小狗", 2);animal.eat();}
}

这时我们发现,程序输出结果:

 此时的你是不是也在想,为什么输出的不是父类的eat方法呢?这里就要讲到重写了:

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

以上面代码为例:

 此时通过父类的引用调用的是子类的eat()方法,我们把这个过程称为动态绑定。

【方法重写的规则】

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

对于第3种情况的代码示意:

 

 【重写和重载的区别】

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

 

【重写的设计原则】

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

例如:若干年前的手机,只能打电话,发短信,来电显示只能显示号码,而今天的手机在来电显示的时候,不仅仅可以显示号码,还可以显示头像,地区等。在这个过程当中,我们不应该在原来老的类上进行修改,因为原来的类,可能还在有用户使用,正确做法是:新建一个新手机的类,对来电显示这个方法重写就好了,这样就达到了我们当今的需求了。

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

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

4. 向上转型和向下转型

4.1 向上转型

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

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

举个“栗”子:

 animal是父类类型,但可以引用一个子类对象,因为是从小范围向大范围的转换。

 【使用场景】

1. 直接赋值

直接赋值:子类对象赋值给父类对象

2. 方法传参

方法传参:形参为父类型引用,可以接收任意子类的对象

3. 方法返回

作返回值:返回任意子类对象

向上转型的优点:让代码实现更简单灵活。

向上转型的缺陷:不能调用到子类特有的方法。

4.2 向下转型

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

 

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 Cat extends Animal {public Cat(String name, int age) {super(name, age);}@Overridepublic void eat() {System.out.println(name + "吃鱼~~~");}public void mew() {System.out.println(name + "喵喵叫");}
}class Dog extends Animal {public Dog(String name, int age) {super(name, age);}@Overridepublic void eat() {System.out.println(name + "吃骨头~~~");}public void bark() {System.out.println(name + "汪汪叫");}
}class Test {public static void main(String[] args) {Animal animal = new Dog("小狗", 1);Dog dog = (Dog) animal; //向下转型dog.bark();}
}

上面的代码时可以成功编译的:

但是,当main方法写成下面这样时:

此时编译器并没有错误提示,但当程序运行的时候就出错了:

在上面的代码中,存在一个类型转换错误。在Test类的main方法中,尝试将一个Animal对象转换为Cat对象,这是不正确的,因为animal实际上是一个Dog对象,无法强制转换为Cat对象。

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

class Test {public static void main(String[] args) {Animal animal = new Dog("小狗", 1);if(animal instanceof Cat){Cat cat = (Cat)animal;cat.mew();}else {System.out.println("dog 无法转化为 cat");}}
}

输出结果: 

 

5. 多态的优缺点

假设有如下代码:

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("❀");}
}

【使用多态的好处】

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

什么叫 "圈复杂度" ? 圈复杂度是一种描述一段代码复杂程度的方式. 一段代码如果平铺直叙, 那么就比较简单容易理解. 而如果有很多的条件分支或者循环语句, 就认为理解起来更复杂. 因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数, 这个个数就称为 "圈复杂度". 如果一个方法的圈复杂度太高, 就需要考虑重构. 不同公司对于代码的圈复杂度的规范不一样. 一般不会超过10。

例如我们现在需要打印的不是一个形状了, 而是多个形状. 如果不基于多态, 实现代码如下:

public static void drawShapes() {Rect rect = new Rect();Cycle cycle = new Cycle();Flower flower = new Flower();String[] shapes = {"cycle", "rect", "cycle", "rect", "flower"};for (String shape : shapes) {if (shape.equals("cycle")) {cycle.draw();} else if (shape.equals("rect")) {rect.draw();} else if (shape.equals("flower")) {flower.draw();}}}

如果使用使用多态, 则不必写这么多的 if - else 分支语句, 代码更简单.

public static void drawShapes() {// 我们创建了一个 Shape 对象的数组.Shape[] shapes = {new Cycle(), new Rect(), new Cycle(),new Rect(), new Flower()};for (Shape shape : shapes) {shape.draw();}}

2. 可扩展能力更强

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

class Triangle extends Shape {@Overridepublic void draw() {System.out.println("△");}
}

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

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

1. 属性没有多态性

        当父类和子类都有同名属性的时候,通过父类引用,只能引用父类自己的成员属性

2. 构造方法没有多态性

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

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

class B {public B() {// do nothingfunc();}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();}
}

代码分析: 

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

 输出结果:

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

 

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

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

相关文章

Linux学习笔记

Linux学习笔记 目录 一&#xff0e; 操作系统的发展历史与linux二&#xff0e; 安装VMWare三&#xff0e; 安装和配置CentOS 7四&#xff0e; Linux操作系统目录结构五&#xff0e; Linux命令 一&#xff0e; 操作系统的发展历史与linux 概述   操作系统产生与发展经历了人工…

VS2022程序集说明汉化

下载本地化的 .NET IntelliSense 文件 https://dotnet.microsoft.com/zh-cn/download/intellisense 目前本地化的 IntelliSense 文件不再可用。 可用的最新版本是 .NET 5。 建议使用英语 IntelliSense 文件。 .NET6的汉化需要自己动手&#xff1a; 教程可以参照下方&#xff1a…

机器人状态估计:robot_localization 功能包使用方法

机器人状态估计&#xff1a;robot_localization 功能包基本使用 前言功能包简介基本使用数据输入与数据输出坐标系设置性能参数调试 前言 移动机器人的状态估计需要用到很多传感器&#xff0c;因为对单一的传感器来讲&#xff0c;都存在各自的优缺点&#xff0c;所以需要一种多…

(文章复现)建筑集成光储系统规划运行综合优化方法matlab代码

参考文献&#xff1a; [1]陈柯蒙,肖曦,田培根等.一种建筑集成光储系统规划运行综合优化方法[J].中国电机工程学报,2023,43(13):5001-5012. 1.基本原理 本文建立的双层耦合模型内、外层分别对应求解容量配置与能量调度问题。外层模型设置光伏与储能容量备选集并将容量配置组合…

笛卡尔积文本的python处理

一 背景 大致背景是这样的&#xff0c;笔者在做数据处理时&#xff0c;遇到一个棘手的事情&#xff0c;主要遇到如下字符串拼接变动的场景&#xff0c;场景主要为&#xff0c;需要考虑如下两张表的组合&#xff1a; 表1-原始文本样式 序号文本样式1A变量B2A变量C3A变量CD4E变…

自然语言处理学习笔记(五)————切分算法

目录 1.切分算法 2.完全切分 3.正向最长匹配 4.逆向最长匹配 5.双向最长匹配 6.速度评测 1.切分算法 词典确定后&#xff0c;句子可能含有很多词典中的词语&#xff0c;他们有可能互相重叠&#xff0c;如何切分需要一些规则。常用规则为&#xff1a;正向匹配算法、逆向匹…

Redis事务、管道

一.Redis事务 1.概念 可以一次执行多个命令&#xff0c;本质是一组命令的集合。一个事务中的所有命令都会序列化&#xff0c;按顺序地串行化执行而不会被其它命令插入&#xff0c;不许加塞 2.Redis事务与数据库事物的区别 3.常用命令 4.事务执行情况 正常执行 即整个过程…

JMeter(二十四)、使用吞吐量控制器实现不同的用户操纵不同的业务

一、需求 需求&#xff1a;博客系统&#xff0c;模拟用户真实行为&#xff0c;80%的用户阅读文章&#xff0c;20%的用户创建文章&#xff0c;创建文章的用户随机的删除或者修改文章。 二、脚本实现 80%的用户查看文章 20%用户创建文章 根据post_id是否能整除2&#xff0c;决…

Python实现GA遗传算法优化BP神经网络回归模型(BP神经网络回归算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 遗传算法&#xff08;Genetic Algorithm&#xff0c;GA&#xff09;最早是由美国的 John holland于20世…

ChatGPT实战:创业咨询,少走弯路,少踩坑

用九死一生形容创业再适合不过&#xff0c;不过一旦成功回报也很诱人&#xff0c;这也是为什么那么多人下场创业。纸上得来终觉浅&#xff0c;绝知此事要躬行&#xff0c;创过业的人都知道其中的心酸&#xff0c;而他们也建议你去创业&#xff0c;因为那真不是一般人能干的事。…

Flutter编译一直显示Running Gradle task ‘assembleDebug‘

&#x1f525; 目前开发的Android Studio版本 &#x1f525; &#x1f525; 当前Flutter SDK 版本 &#x1f525; Flutter 3.10.6 • channel stable • https://github.com/flutter/flutter.git Framework • revision f468f3366c (3 周前) • 2023-07-12 15:19:05 -0700 Eng…

【云原生】Serverless 技术架构分析

一、什么是Serverless? 1、Serverless技术简介 ​ Serverless&#xff08;无服务器架构&#xff09;指的是由开发者实现的服务端逻辑运行在无状态的计算容器中&#xff0c;它由事件触发&#xff0c; 完全被第三方管理&#xff0c;其业务层面的状态则被开发者使用的数据库和存…

PVE虚拟化平台之安装openKylin开源操作系统

PVE虚拟化平台之安装openKylin开源操作系统 一、openKylin介绍1.1 openKylin简介1.2 openKylin特性 二、下载openKylin系统镜像2.1 官方网址2.2 下载openKylin系统镜像 三、上传镜像到PVE存储3.1 检查PVE环境3.2 上传镜像 四、创建虚拟机4.1 设置虚拟机名称4.2 操作系统设置4.3…

mac前端代码编辑 Sublime Text 4 Dev 中文v4.0(4151)

Sublime Text 4 for Mac是一款功能强大的代码编辑器&#xff0c;适合所有需要高效编写代码和进行代码管理的程序员使用。 快速响应&#xff1a;Sublime Text 4在加载文件和执行命令时非常快速&#xff0c;能够让用户在高效的开发过程中体验到无缝的交互。 多种语言支持&#…

LeetCode[207]课程表

难度&#xff1a;Medium 题目&#xff1a; 你这个学期必须选修 numCourses 门课程&#xff0c;记为 0 到 numCourses - 1 。 在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出&#xff0c;其中 prerequisites[i] [ai, bi] &#xff0c;表示如果要学习…

使用上 Spring 的事件机制

本文主要是简单的讲述了Spring的事件机制&#xff0c;基本概念&#xff0c;讲述了事件机制的三要素事件、事件发布、事件监听器。如何实现一个事件机制&#xff0c;应用的场景&#xff0c;搭配Async注解实现异步的操作等等。希望对大家有所帮助。 Spring的事件机制的基本概念 …

xcode 的app工程与ffmpeg 4.4版本的静态库联调,ffmpeg内下的断点无法暂停。

先阐述一下我的业务场景&#xff0c;我有一个iOS的app sdk项目&#xff0c;下面简称 A &#xff0c;以及运行 A 的 app 项目&#xff0c;简称 A demo 。 引用关系为 A demo 引用了 A &#xff0c;而 A 引用了 ffmpeg 的静态库&#xff08;.a文件&#xff09;。此时业务出现了 b…

Linux上安装Keepalived,多台Nginx配置Keepalived(保姆级教程)

目录 一、yum安装 第一步&#xff1a;下载 第二步&#xff1a;编辑Keepalived配置文件&#xff08;第一台&#xff09; 第三步&#xff1a;编辑Keepalived配置文件&#xff08;第二台&#xff09; 第四步&#xff1a;我们在本机利用cmd ping一下 一、yum安装 第一步&…

【数据结构篇】手写双向链表、单向链表(超详细)

文章目录 链表1、基本介绍2、单向链表2.1 带头节点的单向链表测试类&#xff1a;链表实现类&#xff1a; 2.2 不带头节点的单向链表2.3 练习测试类&#xff1a;链表实现类&#xff1a; 3、双向链表测试类&#xff1a;双向链表实现类&#xff1a; 4、单向环形链表**测试类**&…

从零开始实现一个 mini-Retrofit 框架

前言 本篇文章将采用循序渐进的编码方式&#xff0c;从零开始实现一个Retorift框架&#xff0c;在实现过程中不断提出问题并分析实现&#xff0c;最终开发出一个mini版的Retrofit框架 演示一个使用OkHttp的项目Demo 为了更好的演示框架的实现过程&#xff0c;这里我先创建了一…