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