聊聊关于Java方法重写的反思
作者:乐征skyline 时间:2022-10-26 00:36:12
最近在开发中遇到一个关于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) {
}
这种情况下两种写法的行为差异完全可以忽略不计,但是实际开发中我们一般继承自FragmentActivity
或AppCompatActivity
,这两个类都对这个方法做了相应的实现,在这种情况下,第一种写法父类的实现一定会被执行,但是第二种写法可能将父类的实现短路了。这可能导致一些意料之外的问题,比如,Activity和Fragment都对某个requestCode进行处理,但第二种写法会导致Fragment的对应onActivityResult
方法不会被掉用。
在实际开发中我们可能会编写一个BaseActivity
,将一些方法实现一下并添加统计和日志,那么第二种写法也可能导致日志丢失的问题。
问题分析
这个问题让我联想到一个设计原则:里氏替换原则(Liskov Substitution principle)。这个原则说明:派生类(子类)对象可以在程序中代替其基类(超类)对象。这表示程序中任何父类对象可以出现的位置,子类的对象都可将其替代。进一步解读,就是意味着子类可以扩展父类的功能,但不能改变父类原有的功能。
这个原则考虑了安全性。编程时为了降低耦合度,通常面向抽象数据类型(例如接口、抽象类等)来编写,而父类在编写的时候也不会去考虑子类的实现,那么就要求子类的实现的时候需要顾及父类的运行。
那么当我们在重写父类方法的时候,情况就复杂了起来,具体分为以下几种情况:
当父类代码和子类代码都是同一个人负责的时候,并且在代码同一项目、同一模块。这种情况比较安全,因为编写子类实现的人是完全了解并掌控父类实现的;
当父类代码和子类代码是同一个人负责的时候,而代码位于不同项目。例如,一个人同时维护一个应用项目和一个独立框架。这种情况,就可能出隐患,因为随着项目进行,这个框架中的父类可能被多个应用项目使用,这个父类就可能无法兼顾多个项目的场景和用法,而导致子类实现中错误地改写父类的方法。
当父类代码和子类代码时不同的人负责,且代码位于不同项目时,这种情况就比较危险了。因为父类实现的行为实现和行为变更很可能是不透明的、未知的,而且父类的实现可能不会顾及到子类的应用。那么当子类改写父类行为的时候,当父类行为发生变更,那么子类的实现很可能是有问题的。
方法与建议
针对上面所提到的三种情况,我思考了如下三个对应的建议:
针对第一种安全的情况,尽量不改写父类方法,在子类和父类实现中尽量补充注释和注解说明;
针对第二种有隐患的情况,尽量不改写父类方法,父类设计无法涵盖所有场景时,适当时候重构父类代码,而不是让子类通过“hack”的手段曲线救国。
针对第三种危险的情况,一定不要改写父类方法,可以考虑在方法第一行就
super
调用。
来源:https://juejin.cn/post/7236372620999655485
![](/images/zang.png)
![](/images/jiucuo.png)
猜你喜欢
java简单实现复制 粘贴 剪切功能代码分享
java利用java.net.URLConnection发送HTTP请求的方法详解
![](https://img.aspxhome.com/file/2023/3/122313_0s.png)
java获取Date时间的各种方式汇总
volatile与happens-before的关系与内存一致性错误
Android实现点击获取验证码60秒后重新获取功能
![](https://img.aspxhome.com/file/2023/1/109211_0s.gif)
Intellij IDEA如何去掉@Autowired 注入警告的方法
![](https://img.aspxhome.com/file/2023/5/96055_0s.png)
MyBatisPlus 自定义sql语句的实现
c#中实现图片灰度化技术详解
![](https://img.aspxhome.com/file/2023/3/88373_0s.png)
Java 阻塞队列和线程池原理分析
![](https://img.aspxhome.com/file/2023/3/81993_0s.png)
Android实现价格走势自定义曲线图
![](https://img.aspxhome.com/file/2023/0/138080_0s.png)
Flutter学习教程之Route跳转以及数据传递
![](https://img.aspxhome.com/file/2023/0/139450_0s.gif)
java验证电话号码的方法
Android Retrofit的使用详解
Java 容器类源码详解 Set
java 排序算法之快速排序
![](https://img.aspxhome.com/file/2023/4/69314_0s.png)
Java实现插入排序实例
C++深入探究引用的使用
![](https://img.aspxhome.com/file/2023/8/120798_0s.png)
Android基于ViewPager+Fragment实现左右滑屏效果的方法
Android实现图片左右滑动效果
![](https://img.aspxhome.com/file/2023/3/138593_0s.png)