其实这个话题,
本意没想要单独作为一篇文章来叙述,
咱们在上一篇跟踪java集合源码的时候,
其实是给大家留了个口,
原本是想让同学们提出来,
然后有问有答,
想着怎么也比我一人傻呵呵说效果好,
但这两日我实在等不及啦,
想来也没多少可说的,
索性我就主动点儿,
今天把这事儿给办了...
从何说起
到底咱们上文是留了个什么口呢?
大家是否还有印象,
咱们再说集合的【源码跟踪】的环节时,
拿ArrayList类举的例子,
(如下图示):
来看上图红色框框圈起来的位置,
它继承了抽象类AbstractList类 ,
同时实现了List接口,
这无可厚非,
我们继续深入一层,
来看看AbstractList类,
(如下图示):
我们发现AbstractList类在继承了AbstractCollection抽象类,
也同样实现了List接口,
子类为什么会重复继承(实现)父级已继承(实现)的接口(抽象类)呢?
这不是多余了吗?
我们都知道继承关系有传递性,
传递性是啥意思呢?
为了加深理解,
我们先来写一个简单例子,
传递性示例
咱们先创建一个【爷爷】类,
设置一个属性并编写一个方法,
方法很简单,
就是夹带着属性在控制台打印一句话,
(如下图示):
然后创建一个【父】类,
只让它继承【爷爷】类,
类里面啥事儿也不做,
(如下图示):
最后我们再创建一个【儿子】类,
此类里也只继承【父】类,
同样什么也不写,
(如下图示):
接下来验证下继承关系的传递性,
我们创建一个main函数,
然后在此函数体内,
实例化【儿子】(son类)并赋值,
(如下图示):
从上图我们可以看到,
实例化后的子类可以调用属性和方法,
并且能正确执行并打印出我们想要值,
这就证明继承关系是有传递性的。
举例说明
问题又回到开始,
“ArrayList类的父级AbstractList抽象类已经实现过了List接口,
为什么ArrayList还要再次实现List接口”?
为什么java源码里会重复继承(实现)呢?
要说明这个问题,
首先我们要搞明白
抽象类和接口的用法,
我们都知道接口的意义在于,
它定义了一种规范,一种标准,
而不做具体实现,
(当然JDK 1.8及以后的版本,接口有了默认和静态方法,这就另说啦);
所有继承接口的子类都要一 一实现它的所有方法,
这可能就是为什么继承类用关键字“extends”,
继承接口要用“implement”关键字吧?
既然要实现接口的所有方法就存在一个问题,
不是所有要继承接口的子类都能用到接口里的全部方法,
对于继承接口那些用不到的方法也需要实现了它,
这不就造成了代码的重复嘛!
那么怎么解决这个问题呢?
我们可以只让一个类去实现这个接口,
然后其他的类继承这个类就好了嘛。
可能有同学联想到模板,
对,这就是抽象类设计时其中的一种定义,
抽象类就是一种模板式的设计;
这么说可能太苍白,
理解起来不够直观,
让我们来再写个简单的例子,
示例辅助说明
我们首先创建一个接口,
(如下图示):
再接下来我们在创建一个抽象类,
(如下图示):
我们在测试类里继承一下,
分别看看效果,
先来继承(implements)下接口,
(如下图示):
发现继承接口后程序报错了,
我们将鼠标放到红色波浪线看下错误提示,
需要我们将接口内的方法给实现喽,
那我们根据提示将方法salad()实现一下,
(如下图示):
我们发现报错并未解除,
我们点开提示发现,
(如下图示):
根据上图我们看到继承了接口后,
如果不实现接口里方法就会报错,
并提示我们有4个方法待实现,
实现全部方法后的样子,
(如下图示):
我们发现这个时候异常算是解除啦…
那么抽象接口呢?
(如下图示):
我们它没有报任何错误…
并且我们可以根据我们的需求,
实现我们想要实现的方法即可,
比如我们实现salad()方法,
(如下图示):
没有任何的问题….
假如我们有个需求,
除了使用TomatoAbstract里的cooking()方法,
还需要使用FruitInterface里的eat()方法,
按照以往的方式,
我们会这么用,
(如下图示):
可我们只需要用到(标红)两个方法,
就不得不实现那些我们用不到的方法...
解决问题
我们根据上文的分析可以将代码调整下,
或许能解现在的尴尬问题,
我们先进入到TomatoAbstract抽象类,
做如下改动,
(如下图示):
将我们不需要使用的方法,
size()、name()等先在抽象类中实现,
然后切换到我们需要使用的抽象类的方法中,
继承这个TomatoAbstract模板(抽象)类,
(如下图示):
我们发现目前必须要实现的方法只有一个eat(),
实现后我们再来看,
(如下图示):
报错的现象已经解除,
并且此方法也是我们需要使用的方法,
这时候你会说我们用到的还有一个cooking()方法呢?
这个时候我们手动将它实现即可,
(如下图示):
改造后代码是不是简洁、优雅多啦,
这样不仅避免了重复代码、耦合性也更低啦;
为什么会重复继承
有同学可能会说,
这也没有说明白JDK源码里为什么要重复继承的问题啊?
其实JDK源码里之所以重复引用,
更类似于一种编码规范,
为什么这么说呢?
以我们的demo为例,
(如下图示):
从上图我们只能看到此类继承了抽象类TomatoAbstract,
而不能直观的看出eat()方法实现自FruitInterface接口,
所以为了规范我们代码应该改成,
(如下图示):
这样我们就能很容易判断...
以上这两个函数分别继承自FruitInterface 接口和 TomatoAbstract抽象类。
OK啦!!!!
你:就这?
我:就这...
hahahahahaha......
下一篇见…