JAVA中使用到继承就会有两个无法回避的缺点:

  1. 打破了封装性,迫使开发者去了解超类的实现细节,子类和超类耦合。
  2. 超类更新后可能会导致错误。

继承打破了封装性

关于这一点,下面是一个详细的例子(来源于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中要慎重使用继承相关推荐

  1. Java中实现接口与继承的区别

    ** Java中实现接口与继承的区别 ** 首先,先来了解一下什么是接口和继承.接口一般是使用interface来定义的.接口定义同类的定义类似,分为接口的声明和接口体,其中接口体由常量定义和方法定义 ...

  2. java继承类型的用法_详解Java中使用externds关键字继承类的用法

    理解继承是理解面向对象程序设计的关键.在Java中,通过关键字extends继承一个已有的类,被继承的类称为父类(超类,基类),新的类称为子类(派生类).在Java中不允许多继承. (1)继承 cla ...

  3. Java中接口的多继承

    我们知道Java的类只能继承一个类,但可以实现多个接口.但是你知道么?Java中的接口却可以继承多个接口.本文就来说一说Java中接口的多继承. 进入主题之前,先扩展一下.Java为什么只支持单继承呢 ...

  4. 转:Java中子类是否可以继承父类的static变量和方法而呈现多态特性

    原文地址:Java中子类是否可以继承父类的static变量和方法而呈现多态特性 静态方法 通常,在一个类中定义一个方法为static,那就是说,无需本类的对象即可调用此方法,关于static方法,声明 ...

  5. java中序列化之子类继承父类序列化

    原文 父类实现了Serializable,子类不需要实现Serializable 相关注意事项     a)序列化时,只对对象的状态进行保存,而不管对象的方法:     b)当一个父类实现序列化,子类 ...

  6. Java 中子类是否只继承父类的非私有变量和方法?

    今天在 CSDN 论坛中见到有人问在 Java 中子类是否只继承父类的非私有变量和方法(原贴在此).大部分跟贴都认为这句话是正确的,但是对于这个问题背后的本质理解却是错误的. 首先我们明确一下&quo ...

  7. java中super用来定义父类,定义子类必须使用的关键字是 在java中子类若要继承父类,需要使用的关键字是什么...

    java定义接口时需要使用的关键字是 定义接口复使用的关键字是"interface",中文意思"接口.解释:通常接口都是制为了定义百某些规范,而在接口中只定义了方法,而没 ...

  8. 万字总结,一文带你秒懂Java中的封装、继承和多态(有代码 有示例)

    Java中的封装.继承和多态 前言 一.面向对象有什么优势? 二.面向对象的三大特性! 1.封装 1.1访问限定符 1.2关于包的导入 1.2封装 2.继承 2.1继承的语法 2.2父类成员访问 3. ...

  9. Java中代码块和继承

    1.代码块,是用{}括起来的代码. 局部代码块,是用与限定变量的生命周期,及早释放,提高内存利用率. 构造代码块,把多个构造方法中相同的代码可以放到这里,每个构造方法执行前,首先执行构造代码块. 静态 ...

最新文章

  1. js 正则之检测素数
  2. 亚马逊员工流动率150%,每8个月相当于“大换血”,网友:贝佐斯不知足
  3. 二十三种设计模式-六大原则
  4. python读取excel表格-python xlrd读取excel(表格)详解
  5. 法国时隔20年再折桂!“网易云信:世界杯巅峰决战之夜”活动圆满结束!
  6. 计算机组成原理 华南理工,华南理工2017计算机组成原理随堂练习
  7. 使用Expresso学习.net正则表达式
  8. python自动化测试-D6-学习笔记之一(常用模块补充datetime模块)
  9. NET问答: 如何在 ASP.NET Core Web API 的 Response 中添加自定义的 Header ?
  10. 爱因斯坦:量子物理与抽象数学(广义)
  11. 如何在 JavaScript 面试中过五关斩六将?
  12. 使用spring+quartz配置多个定时任务
  13. SpringBoot集成 Shiro
  14. 所有 HTTP 状态代码及其定义
  15. 深度学习——SSR网络配置环境
  16. Python爬取全网文字并词云分析(全程一键化!)
  17. Powershell 添加开机启动项
  18. 创新设计思维记录(part1)
  19. MATLAB中通用桥晶闸管的型号,基于MATLAB的电力电子技术仿真分析
  20. oracle安装界面空白,在windows 2012中安装oracle 12c R2界面空白挂起无响应CPU达到100%的问题...

热门文章

  1. TSqlConnection
  2. linux命令:FTP服务
  3. Java魔法堂:注解用法详解——@SuppressWarnings(转)
  4. CloudStack(二)基础网络模式安装部署
  5. 构建高性能分布式搜索引擎(Wcf-基础篇)一
  6. Node.js折腾记一(改进):文件夹目录树获取
  7. 实验三 类和对象
  8. Python协程深入理解
  9. 第二阶段个人总结10
  10. asp.net中GridView多行表头导出Excel表头显示不全问题解决方法