1.      问题的提出

  在 Java 的集合体系当中,无论是 List(列表)还是 Set(集),在设计的时候都存在一个很奇怪的现象:这两种集合的接口,Java 都为其设计了抽象类 AbstractList 和 AbstractMap,这是模板模式的一种典型实现,在抽象模板中,提供了一些这些集合各自的公共行为的实现。

然而,在这两种集合的典型实现类当中,出现了这种继承和实现的结构:

  • ArrayList:

  • LinkedList:

  • Vector:

  • HashSet:

  • TreeSet:

这些实现类都有一个共同的特征,他们即继承了抽象父类,也实现了集合接口。这两个抽象父类(或其抽象子类),AbstractList、AbstractSet 都各自实现了 List、Set 接口。

  如此一来,实现这些接口就会显得非常多余,因为这些实现类,无论接口的与否,对于功能并不影响。甚至于,从设计的角度来说,这些接口的实现也显得十分多余。我将这种行为称之“接口的重复实现”(类似的设计同样出现在了 Map 结构中)。

  以上的情况,并不是因为版本的更迭,为了提供向下兼容二产生的。通过 Javadoc 可以发现,这些设计都是自 JDK1.2 以来延续至今。

2.      为什么实现接口是多余的?

  关于接口和抽象类,这是一个老生常谈的问题,在这里不想多加赘述。但是我在编码时奉行一个原则:只要不是使用模板模式,接口的优先级是高于抽象类的。这句话反过来理解:如果使用了模板模式,那么抽象类的优先级应该是高于接口实现的。

  以 List 为例,现在我需要新编写一个列表结构的类(不是抽象类),放在我面前有两个选择:AbstractList 和 List。我有以下三种设计方式:

  • 实现 List 接口:我必须重写所有 List 接口中定义的方法,无论这些方法是否是我所必须的。这个工作可能会比较繁琐,但优点在于,我不会遗漏任何一个方法,因为这会报出编译错误。
  • 继承 AbstractList 抽象类:OK,一些公共的行为已经定义了,我会被强制定义所有抽象父类中的声明为 abstract 的方法。但是抽象父类中的一些行为,并不是我所希望的我要重新定义,或者说,我有一些基于自身特性的,更优的解决方案。这种情况并不少见:AbstractList 中实现了基于迭代器 Iterator 的 indexOf() 方法,使用一个指针跟进当前的位置。但是在 ArrayList 中,因为这是基于数组的集合,我可以通过数组的下标直接获取集合的值。但是,这些重新定义的方法需要人为寻找,编译器并不会做出提示。如果只是 indexOf() 之类的方法,那只是性能方面的问题,如果没有重写 remove() 方法,那问题就大了。AbstractList 的 remove() 方法是这么定义的:

   

  • 继承 AbstractList 抽象类的同时,实现 List 接口:这种行为有什么好处吗?再一次的实现 List 接口,能改进2中的弊端吗?或者说,这么做有什么特殊的意义吗?至少从设计的角度来说,我暂时没有找到这么做的优势。

3.      一个勉强的解释

  对于这种“接口的重复定义”现象,我有一个比较勉强的解释。我们常说,接口的作用在于规范一种行为,那么是不是可以这么说,重复定义接口的目的,在于明确行为

  如果说这么一种解释过于宽泛的话,从编码的角度可以这么说:将间接实现接口变为直接实现。如此一来,在 Javadoc 形成的文档中,我可以直观地知道这是一个列表,而不是通过 进入 AbstractList 的 Javadoc,才明确这一点。

  另外,考虑一种极端的情况:如果哪一天需要推翻 AbstractList 这个模板,重复实现可以减低改动代码的风险——当然,这种情况出现的概率很低,如果出现这种情况,最初的设计人员绝对是要被狠狠批判的。(如果真的需要进行这种颠覆性的修改,修改抽象类的代价一定是小于修改接口的,所以被修改的一定是抽象类)

  这种情况在代码中的体现是:在反射抓取接口时,最初只会抓取直接实现的接口,如果想要获得间接实现的接口,需要做更深一步的处理。如下图所示:

4.      以后该怎么做?

  既然 JDK 中给出了这种明确的做法,是不是可以考虑,以后在使用模板模式编码时,将抽象父类所实现的接口,实现类再重新再实现一次?我觉得这个想法是可以好好考虑一下的,在特定的场景下可以这么做,并不强制。原因很简单:在 Java 源码中,单独继承抽象类,或者单独实现接口的情况非常多。

最新文章

  1. RANSAC算法笔记
  2. Spark中决策树源码分析
  3. Centos 7: 打开Samba防火墙端口
  4. 机器学习中的矩阵方法03:QR 分解
  5. linux下使用 Tomcat 的几个坑
  6. ExtJS学习之路第四步:看源码,实战MessageBox
  7. OpenGL基础渲染
  8. ftk学习记录(一个进度条文章)
  9. Codeforces Round #427 (Div. 2)
  10. 23.Linux-块设备驱动(详解)
  11. CSS样式之表格,表单
  12. IOS开发之XCode学习009:UIViewController使用
  13. 【UOJ#311】【UNR #2】积劳成疾(动态规划)
  14. python学习第5天
  15. 剑指offer(21)栈的压入、弹出序列
  16. Redis集群中的节点如何保证数据一致
  17. iOS开发ffmpeg SDK 编译和集成
  18. Keras 如何利用训练好的神经网络进行预测
  19. python基础操作以及其常用内置方法
  20. IOS中文本框输入自动隐藏和自动显示

热门文章

  1. Windows服务管理
  2. spring security 2.x HttpSessionEventPublisher 以及listener配置
  3. UVA 10570 Meeting with Aliens 外星人聚会
  4. java基础—线程(一)
  5. Bootstrap历练实例:交替的进度条
  6. SizeClass介绍
  7. Unity学习之路——主要类
  8. NOIP2018
  9. matlplotlib根据函数画出图形
  10. matplotlib绘图股票走势图实践