一、前言
本文内容摘自《
深入理解Java核心技术:写给Java工程师的干货笔记(基础篇)
》一书,2022年出版,作者
张洪亮(@Hollis)
,阿里巴巴技术专家,著有《
Java工程师成神之路
》系列文章,《
Java工程师成神之路
》电子书已开源,可在阿里云开发者社区免费下载。书籍内容比电子书内容要丰富,内容有修改,有需要的读者可以购买正版书籍。
【如何成神:先搬砖,再砌砖,后造砖!】
本文由 @大白有点菜 原创,请勿盗用,转载请说明出处!如果觉得文章还不错,请点点赞,加关注,谢谢!
《
Java工程师成神之路
》下载地址为:
https://developer.aliyun.com/ebook/395?spm=a2c6h.20345107.ebook-index.24.4c927863j10ats
二、第2章 面向对象的核心概念【继承与实现、多继承、组合与继承】
1、继承与实现
(1)
继承(Inheritance)
:如果多个类的某个部分的功能相同,那么可以抽象出一个类,把它们的相同部分都放到父类里,让它们都继承这个类。
(2)
实现(Implement)
:如果多个类处理的目标是一样的,但是处理的方法、方式不同,那么就定义一个接口,也就是一个标准,让它们都实现这个接口,各自实现自己具体的处理方法。
继承指的是一个类(称为子类、子接口)继承另外一个类(称为父类、父接口)的功能,并可以增加它自己的新功能的能力。所以,继承的根本原因是因为要复用,而实现的根本原因是需要定义一个标准。
在 Java 中,类的继承使用
extends
关键字,而接口的实现使用
implements
关键字。
同样是一台汽车,既可以是电动车,也可以是汽油车,还可以是油电混合汽车,只要遵守不同的标准就行,但是一台车只能属于一个品牌、一个厂商。示例代码:
class Car extends Benz implements GasolineCar, ElectroCar { }
代码中定义了一辆汽车,它实现了电动车和汽油车两个标准,但是它属于奔驰这个品牌,我们可以最大限度地遵守标准,并且复用奔驰车所有已有的一些功能组件。
在接口中只能定义全局常量(static final)和无实现的方法(Java 8 以后可以有defult 方法);而在继承中可以定义属性方法、变量和常量等。
2、多继承
(1)
多继承简介
对于一个类只有一个父类的情况,叫单继承。而一个类同时有多个父类的情况叫多继承。
在Java中,一个类只能通过**
extends
**关键字继承另一个类,不允许多继承。但其它面向对象语言中,如C++是支持多继承的。
(2)
菱形继承问题
假设类B和类C都继承了相同的A,类D通过多重继承机制继承了类B和类C,如图:
因为类D同时继承了类B和类C,并且类B和类C又同时继承了类A,那么,类D就会因为多重继承而继承了两份来自类A中的属性和方法。
在使用类D时,如果想调用一个定义在类A中的方法时,就会出现歧义。
C++为了解决菱形继承问题,引入了虚继承。
因为支持多继承,所以引入了菱形继承问题,又因为要解决菱形继承问题,所以又引入了虚继承。经过分析,实际想要使用多继承的情况并不多。
在Java中,不允许“实现多继承”,即一个类不允许继承多个父类。但是允许“声明多继承”,即一个类可以实现多个接口,一个接口也可以继承多个父接口。
Java8以前,接口只允许方法声明而不允许有方法实现
。
但在Java8中支持了默认函数(Default Method)之后,“Java不支持多继承”。就不是那么绝对了。
虽然无法使用extends同时继承多个类,但是因为有了默认函数,就可能通过implements从多个接口中继承多个默认函数。
3、组合与继承
(1)
继承复用
继承是类与类或者接口与接口之间最常见的一种关系。继承是一种is-a的关系。如图:
is-a:表示“是一个”的关系。
(2)
组合
组合(Composition)体现的是整体与部分之间拥有的关系,即has-a的关系,如图:
has-a:表示“有一个”的关系。
(3)
组合与继承的区别和联系
首先,在**
类的关系确定的时间点
**上,组合和继承是有区别的:
-
继承
:因为在写代码的时候就要指名具体继承哪个类,所以在编译期就确定了类的关系。并且从基类继承的实现是无法在运行期动态改变的,因此降低了应用的灵活性。 -
组合
:在写代码时可以采用面向接口编程,所以类的组合关系一般在运行期确定。
另外,在**
代码复用方式
**上也有区别:
- 在继承结构中,父类的内部细节对于子类是可见的。所以通常说通过继承的代码复用是一种白盒式代码复用。如果基类的实现发生改变,那么派生类的实现也将随之改变。这样就导致了子类行为的不可预知性。
- 组合是通过对现有的对象进行拼装(组合)产生新的、更复杂的功能。因为在对象之间,各自的内部细节是不可见的,所以也说这种方式的代码复用是黑盒式代码复用。因为在组合中一般都定义一个类型,所以在编译期根本不知道具体会调用哪个实现类的方法。
最后,Java中不支持多继承,而组合是没有限制的。
(4)
优缺点对比
组合关系 | 继承关系 |
---|---|
优点:不破坏封装,整体类与局部类之间松耦合,彼此相对独立 | 缺点:破坏封装,子类与父类之间紧密耦合,子类依赖于父类的实现,子类缺乏独立性 |
优点:具有较好的可扩展性 | 缺点:支持扩展,但是往往以增加系统结构的复杂度为代价 |
优点:支持动态组合。在运行时,整体对象可以选择不同类型的局部对象 | 缺点:不支持动态继承。在运行时,子类无法选择不同的父类 |
优点:整体类可以对局部类进行包装,封装局部类的接口,提供新的接口 | 缺点:子类不能改变父类的接口 |
缺点:整体类不能自动获得和局部类同样的接口 | 优点:子类能自动继承父类的接口 |
缺点:创建整体类的对象时,需要创建所有局部类的对象 | 优点:创建子类的对象时,无须创建父类的对象 |
(5)
如何选择
相信很多人都知道面向对象中有一个比较重要的原则“多用组合、少用继承”,或者说“组合优于继承”。在《阿里巴巴Java开发手册》中有一条规定:
谨慎使用继承的方式进行扩展,优先使用组合的方式实现
。
所以,
建议在同样可行的情况下,优先使用组合而不是继承。因为组合更安全、更简单、更灵活、更高效
。
注意,并不是说继承就一点用都没有了,前面说的是“在同样可行的情况下”。有一些场景还是需要使用继承的,或者是更适合使用继承。
继承要慎用,其使用场合仅限于你确信使用该技术有效的情况。一个判断方法是,问一问自己是否需要从新类向基类进行向上转型。如果是必须的,则继承是必要的。反之则应该好好考虑是否需要继承。——《Java编程思想》
只有当子类真正是超类的子类型时,才适合用继承。换句话说,对于两个类 A 和 B,只有当两者之间确实存在 is-a 关系的时候,类 B 才应该继承类 A。——《Effective Java》