集合篇 —— Collection(1):JDK 中的重复实现问题
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 源码中,单独继承抽象类,或者单独实现接口的情况非常多。
集合篇 —— Collection(1):JDK 中的重复实现问题的更多相关文章
- Java集合篇六:Map中key值不可重复的测试
package com.test.collection; import java.util.HashMap; import java.util.Map; //Map中key值不可重复的测试 publi ...
- python删除列表中得重复得数据
解决思想:将列表转换为 集合,利用集合删除重复数据得特性删除重复数据,然后将集合转换为列表 #删除列表中得重复元素 def delect_1 (lt): s = set(lt) lt = list(s ...
- 关于JDK中的集合总结(二)
1.2版本的JDK才出现的java集合框架. 下面介绍说一下Vector的一些特点. import java.util.Enumeration; import java.util.Iterator; ...
- Java中的集合框架-Collection(二)
上一篇<Java中的集合框架-Collection(一)>把Java集合框架中的Collection与List及其常用实现类的功能大致记录了一下,本篇接着记录Collection的另一个子 ...
- 关于JDK中的集合总结(三)
泛型: jdk1.5出现的安全机制. 好处: 1,将运行时期的问题ClassCastException转到了编译时期. 2,避免了强制转换的麻烦. <>:什么时候用?当操作的引用数据类型不 ...
- 关于JDK中的集合总结(一)
静态方法只能继承,不能重写(Override). StringBufffer,数组,对象 都是容器. 加入数据,“zhangsan” true ,67, 三个数据数据类型不同,不能用数组作为集合,只能 ...
- Java 去除 ArrayList 集合中的重复元素
// One practice package Collection; import java.util.ArrayList; import java.util.Iterator; // 去除 Arr ...
- Java中Set集合是如何实现添加元素保证不重复的?
Java中Set集合是如何实现添加元素保证不重复的? Set集合是一个无序的不可以重复的集合.今天来看一下为什么不可以重复. Set是一个接口,最常用的实现类就是HashSet,今天我们就拿HashS ...
- 牛客网Java刷题知识点之Java 集合框架的构成、集合框架中的迭代器Iterator、集合框架中的集合接口Collection(List和Set)、集合框架中的Map集合
不多说,直接上干货! 集合框架中包含了大量集合接口.这些接口的实现类和操作它们的算法. 集合容器因为内部的数据结构不同,有多种具体容器. 不断的向上抽取,就形成了集合框架. Map是一次添加一对元素. ...
随机推荐
- windows网络和共享中心“查看基本网络信息并设置连接”为“未知”的解决方案
存在问题“查看基本网络信息并设置连接”为“未知”.如图所示: 解决步骤 运行services.msc 启动Network List Service 若无法启动,打开其属性,选择“登录”选项卡,将启动类 ...
- Oracle中ROWID详解
oracle数据库的表中的每一行数据都有一个唯一的标识符,或者称为rowid,在oracle内部通常就是使用它来访问数据的.rowid需要 10个字节的存储空间,并用18个字符来显示.该值表明了该行在 ...
- js学习笔记-字符串
1.需要注意的是,JavaScript 的字符串是不可变的(immutable),String 类定义的方法都不能改变字符串的内容.像 String.toUpperCase() 这样的方法,返回的是全 ...
- 实验1 c语言最基本内容
part 1 验证性内容 总结:经受了数组和结构体的双重折磨后,发现这部分好简单...现在没啥问题了... part 2 补全程序 1.判断奇偶 // 程序功能: // 要求用户从键盘输入一个整数 ...
- VueJS坎坷之路111---_self.$scopedSlots.default is not a function
VueJs + Element 话不多说,直接贴错: _self.$scopedSlots.default is not a function <el-table stripe border r ...
- CF-1093 (2019/02/10)
CF-1093 1093A - Dice Rolling 输出x/2即可 #include<bits/stdc++.h> using namespace std; int main() { ...
- 【思维题 线段树】cf446C. DZY Loves Fibonacci Numbers
我这种maintain写法好zz.考试时获得了40pts的RE好成绩 In mathematical terms, the sequence Fn of Fibonacci numbers is de ...
- jCarousel,jQuery下的滚动切换传送插件
转自:http://www.zhangxinxu.com/jq/jcarousel_zh/#Examples 介绍 jCarousel是一款 jQuery 插件, 用来控制水平或垂直排列的列表项. 这 ...
- 17.Yii2.0框架模型添加记录
目录 新建控制器 HomeController.php 新建model Article.php 新建控制器 HomeController.php D:\xampp\htdocs\yii\control ...
- Ubuntu系统里的python
Ubuntu系统里,默认安装python2.7.x版本的python,直接执行python命令,打开的将是python 2.7.x版本:python3版本的需要自行安装,安装成功后,执行python3 ...