当前位置: 首页 > news >正文

关于Java方法重写的一些反思

最近在开发中遇到一个关于Java方法重写的一些问题,对于方法重写的用法以及可能导致的问题产生了一些思考,本文用于记录下这些想法。

问题场景

我们首先来看两段代码:

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {super.onActivityResult(requestCode, resultCode, data);switch (requestCode){case TAKE_PHOTO_CODE:{//处理拍照得到的结果break;}case CHOOSE_FROM_ALBUM_CODE:{//处理相册选取到的结果break;}}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {switch (requestCode){case TAKE_PHOTO_CODE:{//处理拍照得到的结果break;}case CHOOSE_FROM_ALBUM_CODE:{//处理相册选取到的结果break;}default:{super.onActivityResult(requestCode, resultCode, data);}}
}

这两段代码是Android开发中处理Activity结果的示例。Android启动新页面后,新页面设置完结果返回的时候,旧页面可以从这个方法得到新页面的结果。来自不同页面的结果按照参数中的requestCode来区分,这个requestCode和启动新页面时传递的对应,也就是说一个requestCode标识一个页面请求和一个结果类型。例如,上面示例模拟的是常见APP中换用户头像的功能,结果有两种:1. 拍照得到的结果;2. 相册选取得到的结果。

上面两种方法就结果来说都是对的,但是表达的意义不同:第一种写法是纯粹地扩展父类的方法,父类干的事它都干;而第二种写法是改写父类的方法,相当于重定义并依赖了父类的行为,或者说对父类行为做了拦截、访问控制。

原本Activity类中默认实现是个空方法:

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
}

这种情况下两种写法的行为差异完全可以忽略不计,但是实际开发中我们一般继承自FragmentActivityAppCompatActivity,这两个类都对这个方法做了相应的实现,在这种情况下,第一种写法父类的实现一定会被执行,但是第二种写法可能将父类的实现短路了。这可能导致一些意料之外的问题,比如,Activity和Fragment都对某个requestCode进行处理,但第二种写法会导致Fragment的对应onActivityResult方法不会被掉用。

在实际开发中我们可能会编写一个BaseActivity,将一些方法实现一下并添加统计和日志,那么第二种写法也可能导致日志丢失的问题。

问题分析

这个问题让我联想到一个设计原则:里氏替换原则(Liskov Substitution principle)。这个原则说明:派生类(子类)对象可以在程序中代替其基类(超类)对象。这表示程序中任何父类对象可以出现的位置,子类的对象都可将其替代。进一步解读,就是意味着子类可以扩展父类的功能,但不能改变父类原有的功能。

这个原则考虑了安全性。编程时为了降低耦合度,通常面向抽象数据类型(例如接口、抽象类等)来编写,而父类在编写的时候也不会去考虑子类的实现,那么就要求子类的实现的时候需要顾及父类的运行。

那么当我们在重写父类方法的时候,情况就复杂了起来,具体分为以下几种情况:

  1. 当父类代码和子类代码都是同一个人负责的时候,并且在代码同一项目、同一模块。这种情况比较安全,因为编写子类实现的人是完全了解并掌控父类实现的;
  2. 当父类代码和子类代码是同一个人负责的时候,而代码位于不同项目。例如,一个人同时维护一个应用项目和一个独立框架。这种情况,就可能出隐患,因为随着项目进行,这个框架中的父类可能被多个应用项目使用,这个父类就可能无法兼顾多个项目的场景和用法,而导致子类实现中错误地改写父类的方法。
  3. 当父类代码和子类代码时不同的人负责,且代码位于不同项目时,这种情况就比较危险了。因为父类实现的行为实现和行为变更很可能是不透明的、未知的,而且父类的实现可能不会顾及到子类的应用。那么当子类改写父类行为的时候,当父类行为发生变更,那么子类的实现很可能是有问题的。

方法与建议

针对上面所提到的三种情况,我思考了如下三个对应的建议:

  1. 针对第一种安全的情况,尽量不改写父类方法,在子类和父类实现中尽量补充注释和注解说明;
  2. 针对第二种有隐患的情况,尽量不改写父类方法,父类设计无法涵盖所有场景时,适当时候重构父类代码,而不是让子类通过“hack”的手段曲线救国。
  3. 针对第三种危险的情况,一定不要改写父类方法,可以考虑在方法第一行就super调用。
http://www.lryc.cn/news/19593.html

相关文章:

  • 【C语言进阶】文件的顺序读写、随机读写、文本文件和二进制文件、文件读取结束的判定以及文件缓冲区相关知识
  • 图形编辑器:拖拽阻塞优化
  • c++ 的 Eigen库写 AX=XB的矩阵求解代码
  • 正点原子linux驱动篇
  • MATLAB绘制雷达图/蜘蛛图
  • 算法入门,十字路口选择的案例,如果是南方,则向前行
  • 父传子与子传父步骤
  • Java concurrency - Task Execution
  • 浅谈BOM
  • 每日学术速递2.24
  • SpringBoot 面试问答总结(VIP典藏版)
  • CSS 定位网页元素【快速掌握知识点】
  • 构建Docker基础镜像(ubuntu20.04+python3.7.1+chrome101+chromedriver101)
  • 最新最全Java面试知识
  • 个人电脑需求严重疲软,联想集团财务前景仍不乐观
  • 软件测试面试在简历上写了“精通”后,拥有工作经验的我被面试官问到窒息...
  • 色环电容读数方法要点总结
  • C++函数新思想和标准的输入和输出
  • 华为OD机试真题Java实现【汽水瓶】真题+解题思路+代码(20222023)
  • WindownsPowershell中的单引号和双引号
  • 【华为OD机试模拟题】用 C++ 实现 - 数组组成的最小数字(2023.Q1)
  • Ae:使用代理
  • OAuth 2.0 认证和攻击面
  • 论文写作模板
  • (五)物质导数与空间时间导数
  • python实战应用讲解-【语法基础篇】流程控制-运算符(附示例代码)
  • MXNet中使用双向循环神经网络BiRNN对文本进行情感分类
  • SpringBoot 整合 MongoDB 6 以上版本副本集及配置 SSL / TLS 协议
  • C语言static关键字
  • 【华为OD机试模拟题】用 C++ 实现 - 单词接龙(2023.Q1)