浅谈面向对象

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

从某个角度说,你是对的,多态最大的作用就是为了传参提供便利,但我们不应该只看到这一层,还要往下再走走:为什么要用父类引用指向子类实例呢?就好比你看到一把刀很锋利,可以切菜,你不应该疑惑“难道刀就是拿来切菜的吗”,而应该关注“为什么刀可以如此锋利”...

回到你的问题上来,我们更应该关心:为什么可以使用多态机制,以及为什么需要多态?

多态怎么实现的?

我并非计算机专业,所以对于这个问题,只给出一个大概的解释。多态从语法表面上看,就是子类对象可以赋值给父类引用,并且通过该引用可以动态地调用不同子类的方法。

多态按实际用法又可以分为:

  • 继承多态
  • 接口多态

所谓继承多态:

class Son extends Father { 
    @Overrid
    public void smoke() {
        System.out.print("儿子抽烟");
    }
}
class Daughter extends Father {
    @Overrid
    public void smoke() {
        System.out.print("女儿抽烟");
    }
}

// 继承多态,因为Son、Daughter继承了Father
Father obj = new Son();
obj.smoke(); // 打印:儿子抽烟
obj = new Daughter();
obj.smoke(); // 打印:女儿抽烟

所谓接口多态:

class Son implements Swimmer {
    @Override
    public void swim() {
        System.out.print("儿子游泳");
    }
}
class Daughter implements Swimmer {
    @Overrid
    public void swim() {
        System.out.print("女儿游泳");
    }
}

// 接口多态,因为Son、Daughter实现了Swimmer
Swimmer obj = new Son();
obj.swim(); // 打印:儿子游泳
obj = new Daughter();
obj.swim(); // 打印:女儿游泳

实际开发接口多态更常用。多态的实现,依赖于2个大方面:

  • 机制上的支持
  • 编码上的支持

机制支持

首先,编译器要允许这种赋值方式,不然把son赋值给swimmer就会像把 int a赋值给String b一样报错。

其次,运行时要支持并且能通过某种机制找到真正的子类方法。

编码支持

必须存在继承(实现)关系 + 子类必须重写(实现)父类的方法

我们一般所说的多态,其实都是指方法的多态


什么意思呢?以上面Swimmer的代码为例(假设整个工程只有这么几个类),当程序运行时,JVM中实际上并不存在一个对象叫Swimmer,自始至终只有Son和Daughter两个对象,而且Son和Daughter都实现了Swimmer,且重写了swim()方法。当JVM运行到16行时:

JVM是怎么知道要打印“儿子游泳”的呢?换句话说,JVM怎么知道调用Son#swim()而不是Swimmer#swim()或者Daughter#swim()的呢?

这就涉及到所谓的“虚方法”和“虚方法表”了。大家都知道JVM有个所谓的“类加载子系统”,专门负责类的加载(下图最上面的部分)。

而在类加载过程中,有loading、linking、initialization三个阶段,其中linking(链接)阶段又包括3个小阶段:

  • verify(验证)
  • prepare(准备)
  • resolve(解析)

其中在resolve阶段,JVM会针对类或接口、字段、类方法、接口方法等进行相应解析,其中方法信息会形成所谓的“虚方法表”。

也就是说,当出现多态方法调用时,底层会多一次“查表”的过程,也就是通过搜索虚方法表,确定本次实际应该调用的方法(实际指向对象+实例对应的类有无重写父类方法),如果子类Override了父类方法,那么就会执行子类方法。

多态与设计模式

很多初学编程的人,一定会记住两句话,即使他们并不懂得其中含义:

  • 面向对象的三大特性是:封装、继承、多态
  • 万物皆对象

但在我眼里,封装、继承这俩货和多态根本不是一个档次的(就好比李云迪和郎朗),多态才是面向对象的核心和根本,甚至没有多态就没有面向对象。举个例子,C语言没有封装吗?不也是可以抽取方法吗?也有结构体呢,看起来不像对象吗?再者,你问问自己,你使用继承是为了什么?不就是为了贪图父类的那一点点已经写好的方法,为了偷点懒吗?既然是为了少写一点代码,我抽取成方法不行吗?

所以,到底什么是面向对象呢?

这就回到了我上面说的,多态才是面向对象的核心(当然,面向对象本质是一种编程思想的转变)。当我们有了多态,才能写出更加抽象的代码,而抽象代表稳定

假设世界末日,外星人占领地球了,它们觉得必须杀鸡儆猴,我们因为真的打不过,只能任由宰割。此时我们签订契约:你们可以杀一个动物。于是我们送了一只实验室的小白鼠,因为小白鼠也是动物呀。动物这个词是抽象的,后面我们送啥都可以,只要不送人。

再举个编程的例子。假设在写好的一个类文件中,你写下这样一段代码:

如果后期接入拼多多,你就需要修改代码。但如果使用策略模式,就可以用增量的方式代替修改(开闭原则):

其实,这就是策略模式。而所谓的设计模式,其实有一本书的书名,恰恰点破了设计模式的本质:

是的,设计模式本质是围绕着“在面向对象的基础上,如何复用设计”这个原则展开的...所以本质又回到了面向对象。为什么设计模式这么牛逼,能把很多看起来像“屎山一样”的代码优化得清晰、简洁?本质上就是多态!

所谓“屎山一样”的代码,大概率就是因为后期需求不断迭代,开发人员在未经思考的情况下肆意使用if else添加逻辑分支导致的!但分支是不会无缘无故消失的,只是借助设计模式把分支下推,最终交给了多态——JVM,你给我去查虚方法表。

换句话说就是:JVM,这坨屎你来吃。

最终,JVM带着虚方法表承受了一切,而我们的上层代码一扫阴霾,看起来干净而整洁,也就是所谓的clean code...

所以,最后再问一句:

多态真的就是用来传参吗?

Ps.也正因为多态调用底层需要查虚方法表,所以大部分设计模式的引入其实反而会降低执行效率(可以忽略),也可能增加内存负担(子类和子类对象增多)。但我们必须清楚,设计模式本来就不是为了解决效率问题,而是为了解决扩展问题,让编码复用性更高、更清晰。只有极少部分设计模式的初衷是为了效率和内存经济性,比如享元模式(Integer、Long这些包装类底层有缓存池)。

补充说明

有同学容易把虚方法表和对象方法调用搞混了,这两个其实是完全不同维度的东西,这里补充解释一下。

上一篇《对象与this》,我们解释了一个问题:

Person的changeUser()方法是所有Person实例p1、p2共有的,那么p1.changeUser()为什么不会处理p2的数据呢?

根本原因是p1.changeUser()会隐式传递this,那么执行changeUser()时,虽然方法是p1、p2共用的,但只会处理p1的。

根据Person类,可以new出无数个对象 p1、p2...pn,但每个对象调用changeUser()时都会传递不同的this,那么changeUser()同一套指令(方法),处理的数据(对象字段)就是不同的。

而上面所说的虚方法表,它的创建时机是类加载阶段,而所谓类加载,可以认为是类层次的,此时并没有业务对象被创建(比如Person对象),但确实又是在运行时起作用。两者的关系其实是这样的:

调父还是子的方法(重载),与这个方法处理哪个对象(隐式this),是两个不同维度的东西。

最后,无论new多少个对象,虚方法表都是不变的(类层次)。

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

进群,大家一起学习,一起进步,一起对抗互联网寒冬

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

用公式告诉你 现货黄金投资者要不要换策略?

看过笔者相关文章的朋友都知道,其实笔者是相当不鼓励投资者更改策略的。但这并不意味着,策略不能改或者换。之所以反对更改策略,是因为很多人对自己的策略还没上手,没了解清楚就急着换策略,这是没必要的。通过下面这个…

RK3568 AD按键改成GPIO按键

authordaisy.skye的博客_CSDN博客-嵌入式,Qt,Linux领域博主 相对路径 kernel/arch/arm64/boot/dts/rockchip/ido-evb3568-v2b.dtsi 代码解析 linux,input-type <1>;//input类型 <EV_KEY>按键 即1 gpios <&gpio1 RK_PB2 GPIO_ACTIVE_HIGH>;//io脚地址…

算法--- 叶子相似的树

题目 请考虑一棵二叉树上所有的叶子&#xff0c;这些叶子的值按从左到右的顺序排列形成一个 叶值序列 。 举个例子&#xff0c;如上图所示&#xff0c;给定一棵叶值序列为 (6, 7, 4, 9, 8) 的树。 如果有两棵二叉树的叶值序列是相同&#xff0c;那么我们就认为它们是 叶相似…

app小程序定制开发的优势|企业软件网站建设

app小程序定制开发的优势|企业软件网站建设 小程序定制开发是目前互联网行业中备受关注的领域之一。随着智能手机的普及和移动互联网的迅猛发展&#xff0c;越来越多的企业和个人开始重视小程序的潜力&#xff0c;并积极寻求定制开发的服务。那么&#xff0c;为什么小程序定制开…

[干货]LangChain入门-LangChain框架的构成与特点

什么是LangChain? LangChain是一个强大的框架&#xff0c;旨在帮助开发人员使用语言模型构建端到端的应用程序。它提供了一套工具、组件和接口&#xff0c;可简化创建由大型语言模型 (LLM) 和聊天模型提供支持的应用程序的过程。LangChain可以轻松管理与语言模型的交互&#…

8086/8088 存储器分段概念

8086/8088 存储器分段概念 这一节主要讲述8086/8088 存储器分段的概念。 目的 从8086 CPU开始采用了分段的方法管理存储器&#xff0c;只有充分理解存储器分段的概念和存储器逻辑地址和物理地址的关系&#xff0c;才能有助于我们掌握8086/8088汇编语言。 存储器分段的原因 在此…

服务器探针-serverstatus

{alert type"info"} 之前给大家介绍过一个简单的服务器监控。uptime-kuma 今天给各位带来一个酷炫的多服务器探针和多服务器监控。ServerStatus {/alert} 作者的开源项目地址如下&#xff1a;https://github.com/cppla/ServerStatus 作者的项目体验地址如下 https://…

Unity中 Start和Awake的区别

Awake和Start在Unity中都是MonoBehaviour脚本中的生命周期函数 Awake函数在游戏对象首次被加载时调用&#xff0c;在游戏对象初始化之前调用。 start函数在游戏对象初始化完成后调用&#xff0c;在update第一次执行前调用。 这两个函数在其生命周期内都只会调用一次&#xf…

为什么同样是做测试,别人年薪30W+?我10k!!!

作为一名初出茅庐的软件测试员&#xff0c;职业发展的道路的确蜿蜒曲折&#xff0c;面对一次次的岗位竞争&#xff0c;挑战一道道的面试关卡&#xff0c;一边带着疑惑&#xff0c;一边又要做出选择&#xff0c;只能无奈的感叹&#xff1a;比你优秀的人比你还努力&#xff0c;你…

iPaaS和RPA,企业自动化应该如何选择?

全球著名的咨询调查机构Gartner在2022年初再次发布了《2022年12大技术趋势》报告。 Gartner是全球最具权威的IT研究与顾问咨询公司&#xff0c;成立于1979年&#xff0c;在界定及分析那些决定了商业进程的发展趋势与技术方面&#xff0c;它拥有二十年以上的丰富经验&#xff0c…

【Vue】Vue3 超简单拖拽条动态修改容器宽度

demo 代码 const leftBoxWidth ref(200); // 默认宽度 const leftResize (e: MouseEvent) > {const startX e.clientX;const startWidth leftBoxWidth.value;const mouseMove (documentE: MouseEvent) > {// 80 是左侧菜单宽度leftBoxWidth.value startWidth docu…

深度学习论文解读:比较ResNet和ViT差异

前言 计算机视觉、机器学习&#xff0c;这两个词会让你想到什么&#xff1f; 相信绝大多数人第一反应都是CNN&#xff0c;而持续关注这些领域发展的人&#xff0c;则会进一步联想到近几年大火的Transformer&#xff0c;它不仅在自然语言相关任务上表现优秀&#xff0c;在图像…

HarmonyOS4.0系列——01、下载、安装、配置环境、搭建页面以及运行示例代码

HarmonyOS4.0应用开发 安装编辑器 这里安装windows版本为例 安装依赖 打开DevEco Studio 这八项全部打钩即可开始编写代码&#xff0c;如果存在x&#xff0c;需要安装正确的库即可 开发 点击Create Project 选择默认模板——next Model部分分为Stage和FA两个应用模型&…

JS:给数字添加千分位符(每3位数用逗号隔开)

背景 如果一串数字的长度太长&#xff0c;就不方便阅读&#xff0c;因此可以采用分隔符对数字进行分割本文的分割规则是&#xff1a; 如果数字的长度大于等于5则进行分割&#xff0c;每3位数用逗号分割开 解决 数字可以分为&#xff1a;number类型的数字和字符串类型的数字&…

作为电子信息工程的学生,学完stm32后,是应该学fpga还是Linux?

作为电子信息工程的学生&#xff0c;学完stm32后&#xff0c;是应该学fpga还是Linux? 我自己也是电子信息工程专业出身&#xff0c;FPGA和Linux都是我们大三的专业课&#xff0c;都学过基础也做了基础 的项目&#xff0c;我自己的选择是Linux。最近很多小伙伴找我&#xff0c…

汽车制造领域中的3D测量仪,您了解多少?

在汽车制造领域中&#xff0c;3D测量仪可以满足各种不同的测量需求&#xff0c;被广泛应用于各种零部件和整车的测量、检测及质量控制&#xff0c;为汽车制造的质量控制和精度提高提供了重要的保障。 在汽车设计阶段&#xff0c;3D测量仪可以精确测量车身、引擎盖、车门等零部…

主播个人风格定位

—、外观风格 主播的外观风格是给观众的第一印象&#xff0c;它决定了主播在观众心中的形象和定位。以下是一些常见的外观风格类型: 1.时尚型:主播穿着时尚、前卫&#xff0c;通常以潮流、新颖的形象出现在观众面前。 2.清新自然型:主播穿着简单、舒适&#xff0c;给人一种自…

亚马逊卖家售后管理做得好,差评投诉不见了!一文分享售后管理技巧

对于亚马逊卖家而言&#xff0c;把产品卖出去之后并非一劳永逸&#xff0c;往往都会涉及到售后服务&#xff0c;即使卖家发出了货物且收到了付款&#xff0c;买家也可能申请售后退货退款。售后服务是亚马逊卖家成功的关键之一&#xff0c;这不仅仅影响着买家的购买体验&#xf…

如何找到自己的兴趣和擅长,并以此为职业?

说起热爱&#xff0c;擅长&#xff0c;兴趣&#xff0c;职业这些词&#xff0c;我是一贯的主张&#xff1a;人活着不容易&#xff0c;干点自己喜欢的不香吗。 但是.....一定要注意这个但是...... 什么是兴趣&#xff1f; 三分钟热度算不算&#xff1f;不能养家糊口的算不算&…

2024年全网最全的Jmeter教程:jmeter_BeanShell脚本通过BeanShell进行加解密方法

BeanShell脚本 BeanShell简介&#xff1a; BeanShell是一种完全符合Java语法规范的脚本语言,并且又拥有自己的一些 语法和方法&#xff1b; BeanShell是一种松散类型的脚本语言&#xff1b; BeanShell是用Java写成的&#xff0c;一个小型的、免费的、可以下载、嵌入式的 Ja…