为什么说Java中要慎重使用继承

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

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

关于这一点,下面是一个详细的例子(来源于Effective Java第16条)

这里自定义了一个HashSet,重写了两个方法,它和超类唯一的区别是加入了一个计数器,用来统计添加过多少个元素。

写一个测试来测试这个新增的功能是否工作:

运行后会发现,加入了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()重写了这些操作,那么就很可能导致错误产生。

关键在于,子类的写法很可能从表面上看来没有问题,但是却会出错,这就迫使开发者去了解超类的实现细节,从而打破了面向对象的封装性,因为封装性是要求隐藏实现细节的。更危险的是,错误不一定能轻易地被测出来,如果开发者不了解超类的实现细节就进行重写,那么可能就埋下了隐患。

超类更新时可能产生错误

这一点比较好理解,主要有以下几种可能:

超类更改了已有方法的签名。会导致编译错误。超类新增了方法:和子类已有方法的签名相同但返回类型不同,会导致编译错误。和子类的已有方法签名相同,会导致子类无意中复写,回到了第一种情况。

和子类无冲突,但可能会影响程序的正确性。比如子类中元素加入集合必须要满足特定条件,这时候如果超类加入了一个无需检测就可以直接将元素插入的方法,程序的正确性就受到了威胁。

设计可继承的类设计可以用来继承的类时,应该注意:

对于存在自用性的可覆盖方法,应该用文档精确描述调用细节。尽可能少的暴露受保护成员,否则会暴露太多实现细节。构造器不应该调用任何可覆盖的方法。详细解释下第三点。它实际上和 继承打破了封装性 里讨论的问题很相似,假设有以下代码:

上述代码在运行测试时就会抛出NullPointerException :

因为超类的构造函数会在子类的构造函数之前先运行,这里超类的构造函数对someMethod()有依赖,同时someMethod()被重写,所以超类的构造函数里调用到的将是Son.someMethod(),而这时候子类还没被初始化,于是在运行到date.getTime()时便抛出了空指针异常。

因此,如果在超类的构造函数里对可覆盖的方法有依赖,那么在继承时就可能会出错。

结论慎重使用继承,复合优先于继承。

使用继承时重写超类中存在自用性的可覆盖方法可能会出错,即使不进行重写,超类更新时也可能会引入错误。

如果使用继承和复合皆可,那么优先使用复合,上述关于继承的缺点都可以用复合来避免。

如果要使用继承,那么应该精心设计超类,并提供详细文档。

如果你在Java的开发工作中遇到困难,或者学习遇到瓶颈,欢迎加入我们的Java总群:309603235,群内有Java的技术大牛,欢迎菜鸟,老鸟入坑。

原文地址:http://blog.51cto.com/13672983/2119787

时间: 2024-10-09 13:09:43

为什么说Java中要慎重使用继承的相关文章

详解Java中代码块和继承

本文发表于个人GitHub主页,原文请移步详解Java中代码块和继承 阅读. 概念 1.代码块 局部代码块 用于限定变量生命周期,及早释放,提高内存利用率 静态代码块 对类的数据进行初始化,仅仅只执行一次. 构造代码块 把多个构造方法中相同的代码可以放到这里,每个构造方法执行前,首先执行构造代码块. 2.继承 继承是已有的类中派生出新的类,新的类能够吸收已有类的数据属性和行为,并能扩展新的功能. 代码块的执行顺序 public class Test {    public String name

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

原文地址:Java中子类是否可以继承父类的static变量和方法而呈现多态特性 静态方法 通常,在一个类中定义一个方法为static,那就是说,无需本类的对象即可调用此方法,关于static方法,声明为static的方法有以下几条限制: 它们仅能调用其他的static 方法. 它们只能访问static数据. 它们不能以任何方式引用this 或super. 无论是static修饰的变量,还是static修饰的方法,我们都知道他们是属于类本身的,不是属于某一个对象的,当声明一个对象时,并不产生sta

java中阻止类的继承

1.使用final来修饰类 final表示这个类是继承树的末端,不能被继承. 2.将类的构造方法声明为private的,再提供一个static的方法来返回一个类的对象. JAVA语言要求继承时必须在构造器里的第一行来调用(call)超类(super class)的构造器. 这个是启动继承特征所必须的. 在JAVA中,我们通过调用super()这个方法来完成这个任务,它将会映射到一个超类的构造器中. 如果你没有给父类提供一个默认的构造器,那么编译器将会插入一个默认的超类构件器用来调用.当将父类的构

Java面试题:Java中的集合及其继承关系

关于集合的体系是每个人都应该烂熟于心的,尤其是对我们经常使用的List,Map的原理更该如此.这里我们看这张图即可: 1.List.Set.Map是否继承自Collection接口? List.Set 是,Map 不是.Map是键值对映射容器,与List和Set有明显的区别,而Set存储的零散的元素且不允许有重复元素(数学中的集合也是如此),List是线性结构的容器,适用于按数值索引访问元素的情形. 2.阐述ArrayList.Vector.LinkedList的存储性能和特性. ArrayLi

Java中接口是否可以继承多个接口?

可以. 接口是常量值和方法定义的集合.接口是一种特殊的抽象类. java类是单继承的.classB Extends classA java接口可以多继承.Interface3 Extends Interface0, Interface1, interface…… 不允许类多重继承的主要原因是,如果A同时继承B和C,而B和C同时有一个D方法,A如何决定该继承那一个呢? 但接口不存在这样的问题,接口全都是抽象方法继承谁都无所谓,所以接口可以继承多个接口. 注意: 1)一个类如果实现了一个接口,则要实

java中到底什么是继承?

在上图中,(视频下载) (全部书籍)对于车来讲,汽车就是子类.对于汽车来讲,奔驰就是子类.车是汽车的基类,超类,或说父类.到底什么是继承?马克-to-win,子类把父类的方法和属性当成自己的一样随便用的这种现象叫继承.In OOP, the ability that subclass inherits all of the variables and methods defined in the superclass is known as Inheritance. 继承是一种"是"的

java中子类能不能继承父类构造方法

首先来看一下下面这个例子: 结果有些和想象中的不一样吧. 原因如下:其实每个子类构造方法的第一条语句,都是隐含地调用super(),如果父类没有这种形式的构造函数,那么在编译的时候就会报错. 所以父类中的构造方法是不能继承的,但是在实例化子类的时候会调用父类的构造方法,这样就能解释下面这种情况了.

使用java中,面向对象封装+继承的方法算题

1.第一种:给定一行字符,逆序输出此字符串(空格.数字不输出),如"ab 23,(4 cd"输出"dc(,ba".(要求:使用面向对象封装+继承) class Bu { private String str; public Bu(){} public Bu(String str){ this.str = str; } public String getStr(){ return str; } public void setStr(String str){ this.

JAVA中的继承

1.什么是继承 基于一个已存在的类,创建一个新的类.已存在的类即父类,新的类即子类,继承就是子类继承并拥有父类的属性和方法,同时,子类还有拥有父类所不具有的属性和方法. 父类,也称为基类.超类(superclass):子类,也称为派生类. 2.JAVA中"继承"的特点 JAVA中一个类只能继承一个父类.不像C++等语言那样,可以继承多个类.这也是JAVA比较容易学的一方面 只能继承父类中非private成员属性和方法,private是父类所特有的不能继承 3.JAVA中的"继