为什么说Java中要慎重使用继承
JAVA中使用到继承就会有两个无法回避的缺点:
- 打破了封装性,迫使开发者去了解超类的实现细节,子类和超类耦合。
- 超类更新后可能会导致错误。
继承打破了封装性
关于这一点,下面是一个详细的例子(来源于Effective Java第16条)
public class MyHashSet<E> extends HashSet<E> {private int addCount = 0;public int getAddCount() {return addCount;}@Overridepublic boolean add(E e) {addCount++;return super.add(e);}@Overridepublic boolean addAll(Collection<? extends E> c) {addCount += c.size();return super.addAll(c);}
}
复制代码
这里自定义了一个HashSet
,重写了两个方法,它和超类唯一的区别是加入了一个计数器,用来统计添加过多少个元素。
写一个测试来测试这个新增的功能是否工作:
public class MyHashSetTest {private MyHashSet<Integer> myHashSet = new MyHashSet<Integer>();@Testpublic void test() {myHashSet.addAll(Arrays.asList(1,2,3));System.out.println(myHashSet.getAddCount());}
}
复制代码
运行后会发现,加入了3个元素之后,计数器输出的值是6。
进入到超类中的addAll()
方法就会发现出错的原因:它内部调用的是add()
方法。所以在这个测试里,进入子类的addAll()
方法时,数器加3,然后调用超类的addAll()
,超类的addAll()
又会调用子类的add()
三次,这时计数器又会再加三。
问题的根源
将这种情况抽象一下,可以发现出错是因为超类的可覆盖的方法存在
(即超类里可覆盖的方法调用了别的可覆盖的方法),这时候如果子类覆盖了其中的一些方法,就可能导致错误。
比如上图这种情况,Father
类里有可覆盖的方法A
和方法B
,并且A
调用了B
。子类Son
重写了方法B
,这时候如果子类调用继承来的方法A
,那么方法A
调用的就不再是Father.B()
,而是子类中的方法Son.B()
。如果程序的正确性依赖于Father.B()
中的一些操作,而Son.B()
重写了这些操作,那么就很可能导致错误产生。
关键在于,子类的写法很可能从表面上看来没有问题,但是却会出错,这就迫使开发者去了解超类的实现细节,从而打破了面向对象的封装性,因为封装性是要求隐藏实现细节的。更危险的是,错误不一定能轻易地被测出来,如果开发者不了解超类的实现细节就进行重写,那么可能就埋下了隐患。
超类更新时可能产生错误
这一点比较好理解,主要有以下几种可能:
- 超类更改了已有方法的签名。会导致编译错误。
- 超类新增了方法:
- 和子类已有方法的签名相同但返回类型不同,会导致编译错误。
- 和子类的已有方法签名相同,会导致子类无意中复写,回到了第一种情况。
- 和子类无冲突,但可能会影响程序的正确性。比如子类中元素加入集合必须要满足特定条件,这时候如果超类加入了一个无需检测就可以直接将元素插入的方法,程序的正确性就受到了威胁。
设计可继承的类
设计可以用来继承的类时,应该注意:
- 对于存在自用性的可覆盖方法,应该用文档精确描述调用细节。
- 尽可能少的暴露受保护成员,否则会暴露太多实现细节。
- 构造器不应该调用任何可覆盖的方法。
详细解释下第三点。它实际上和
里讨论的问题很相似,假设有以下代码:
public class Father {public Father() {someMethod();}public void someMethod() {}
}
复制代码
public class Son extends Father {private Date date;public Son() {this.date = new Date();}@Overridepublic void someMethod() {System.out.println("Time = " + date.getTime());}
}
复制代码
上述代码在运行测试时就会抛出NullPointerException
:
public class SonTest {private Son son = new Son();@Testpublic void test() {son.someMethod();}
}
复制代码
因为超类的构造函数会在子类的构造函数之前先运行,这里超类的构造函数对someMethod()
有依赖,同时someMethod()
被重写,所以超类的构造函数里调用到的将是Son.someMethod()
,而这时候子类还没被初始化,于是在运行到date.getTime()
时便抛出了空指针异常。
因此,如果在超类的构造函数里对可覆盖的方法有依赖,那么在继承时就可能会出错。
结论
慎重使用继承,复合优先于继承。
使用继承时重写超类中存在自用性的可覆盖方法可能会出错,即使不进行重写,超类更新时也可能会引入错误。
如果使用继承和复合皆可,那么优先使用复合,上述关于继承的缺点都可以用复合来避免。
如果要使用继承,那么应该精心设计超类,并提供详细文档。
为什么说Java中要慎重使用继承相关推荐
- Java中实现接口与继承的区别
** Java中实现接口与继承的区别 ** 首先,先来了解一下什么是接口和继承.接口一般是使用interface来定义的.接口定义同类的定义类似,分为接口的声明和接口体,其中接口体由常量定义和方法定义 ...
- java继承类型的用法_详解Java中使用externds关键字继承类的用法
理解继承是理解面向对象程序设计的关键.在Java中,通过关键字extends继承一个已有的类,被继承的类称为父类(超类,基类),新的类称为子类(派生类).在Java中不允许多继承. (1)继承 cla ...
- Java中接口的多继承
我们知道Java的类只能继承一个类,但可以实现多个接口.但是你知道么?Java中的接口却可以继承多个接口.本文就来说一说Java中接口的多继承. 进入主题之前,先扩展一下.Java为什么只支持单继承呢 ...
- 转:Java中子类是否可以继承父类的static变量和方法而呈现多态特性
原文地址:Java中子类是否可以继承父类的static变量和方法而呈现多态特性 静态方法 通常,在一个类中定义一个方法为static,那就是说,无需本类的对象即可调用此方法,关于static方法,声明 ...
- java中序列化之子类继承父类序列化
原文 父类实现了Serializable,子类不需要实现Serializable 相关注意事项 a)序列化时,只对对象的状态进行保存,而不管对象的方法: b)当一个父类实现序列化,子类 ...
- Java 中子类是否只继承父类的非私有变量和方法?
今天在 CSDN 论坛中见到有人问在 Java 中子类是否只继承父类的非私有变量和方法(原贴在此).大部分跟贴都认为这句话是正确的,但是对于这个问题背后的本质理解却是错误的. 首先我们明确一下&quo ...
- java中super用来定义父类,定义子类必须使用的关键字是 在java中子类若要继承父类,需要使用的关键字是什么...
java定义接口时需要使用的关键字是 定义接口复使用的关键字是"interface",中文意思"接口.解释:通常接口都是制为了定义百某些规范,而在接口中只定义了方法,而没 ...
- 万字总结,一文带你秒懂Java中的封装、继承和多态(有代码 有示例)
Java中的封装.继承和多态 前言 一.面向对象有什么优势? 二.面向对象的三大特性! 1.封装 1.1访问限定符 1.2关于包的导入 1.2封装 2.继承 2.1继承的语法 2.2父类成员访问 3. ...
- Java中代码块和继承
1.代码块,是用{}括起来的代码. 局部代码块,是用与限定变量的生命周期,及早释放,提高内存利用率. 构造代码块,把多个构造方法中相同的代码可以放到这里,每个构造方法执行前,首先执行构造代码块. 静态 ...
最新文章
- js 正则之检测素数
- 亚马逊员工流动率150%,每8个月相当于“大换血”,网友:贝佐斯不知足
- 二十三种设计模式-六大原则
- python读取excel表格-python xlrd读取excel(表格)详解
- 法国时隔20年再折桂!“网易云信:世界杯巅峰决战之夜”活动圆满结束!
- 计算机组成原理 华南理工,华南理工2017计算机组成原理随堂练习
- 使用Expresso学习.net正则表达式
- python自动化测试-D6-学习笔记之一(常用模块补充datetime模块)
- NET问答: 如何在 ASP.NET Core Web API 的 Response 中添加自定义的 Header ?
- 爱因斯坦:量子物理与抽象数学(广义)
- 如何在 JavaScript 面试中过五关斩六将?
- 使用spring+quartz配置多个定时任务
- SpringBoot集成 Shiro
- 所有 HTTP 状态代码及其定义
- 深度学习——SSR网络配置环境
- Python爬取全网文字并词云分析(全程一键化!)
- Powershell 添加开机启动项
- 创新设计思维记录(part1)
- MATLAB中通用桥晶闸管的型号,基于MATLAB的电力电子技术仿真分析
- oracle安装界面空白,在windows 2012中安装oracle 12c R2界面空白挂起无响应CPU达到100%的问题...