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 中的重复实现问题的更多相关文章

  1. Java集合篇六:Map中key值不可重复的测试

    package com.test.collection; import java.util.HashMap; import java.util.Map; //Map中key值不可重复的测试 publi ...

  2. python删除列表中得重复得数据

    解决思想:将列表转换为 集合,利用集合删除重复数据得特性删除重复数据,然后将集合转换为列表 #删除列表中得重复元素 def delect_1 (lt): s = set(lt) lt = list(s ...

  3. 关于JDK中的集合总结(二)

    1.2版本的JDK才出现的java集合框架. 下面介绍说一下Vector的一些特点. import java.util.Enumeration; import java.util.Iterator; ...

  4. Java中的集合框架-Collection(二)

    上一篇<Java中的集合框架-Collection(一)>把Java集合框架中的Collection与List及其常用实现类的功能大致记录了一下,本篇接着记录Collection的另一个子 ...

  5. 关于JDK中的集合总结(三)

    泛型: jdk1.5出现的安全机制. 好处: 1,将运行时期的问题ClassCastException转到了编译时期. 2,避免了强制转换的麻烦. <>:什么时候用?当操作的引用数据类型不 ...

  6. 关于JDK中的集合总结(一)

    静态方法只能继承,不能重写(Override). StringBufffer,数组,对象 都是容器. 加入数据,“zhangsan” true ,67, 三个数据数据类型不同,不能用数组作为集合,只能 ...

  7. Java 去除 ArrayList 集合中的重复元素

    // One practice package Collection; import java.util.ArrayList; import java.util.Iterator; // 去除 Arr ...

  8. Java中Set集合是如何实现添加元素保证不重复的?

    Java中Set集合是如何实现添加元素保证不重复的? Set集合是一个无序的不可以重复的集合.今天来看一下为什么不可以重复. Set是一个接口,最常用的实现类就是HashSet,今天我们就拿HashS ...

  9. 牛客网Java刷题知识点之Java 集合框架的构成、集合框架中的迭代器Iterator、集合框架中的集合接口Collection(List和Set)、集合框架中的Map集合

    不多说,直接上干货! 集合框架中包含了大量集合接口.这些接口的实现类和操作它们的算法. 集合容器因为内部的数据结构不同,有多种具体容器. 不断的向上抽取,就形成了集合框架. Map是一次添加一对元素. ...

随机推荐

  1. oracle 11g r2安装

    安装环境:windows 7 安装版本:Oracle_win32_11gR2 目的:用于模拟服务器环境,学习Oracle操作 1. 下载oracle 11g r2,下载地址:www.oracle.co ...

  2. Java 文件操作-RandomAccessFile

    1. RandomAccessFile     Java提供了一个可以对文件随机访问的操作,访问包括读和写操作.该类名为RandomAccessFile.该类的读写是基于指针的操作. 1)文件访问模式 ...

  3. UWP开发:应用文件存储

    应用设置由于数据量和数据类型的限制,有很大的局限性,所以还需要应用文件存储,以文件的方式存储数据.在每个应用的应用数据存储中,该应用拥有系统定义的根目录:一个用于本地文件,一个用于漫游文件,还有一个用 ...

  4. cocos2dx for lua 加密图片

    图片加密的方法有很多种,在cocos2dx中,经常会使用TexturePacker来加密图片,方法如下: 打开TexturePacker,点击Add Sprite添加图片,在output栏下的Text ...

  5. Codevs1081 线段树练习 2

    题目描述 Description 给你N个数,有两种操作 1:给区间[a,b]的所有数都增加X 2:询问第i个数是什么? 输入描述 Input Description 第一行一个正整数n,接下来n行n ...

  6. luogu4608 [FJOI2016]所有公共子序列问题

    题目描述: luogu loj 题解: 序列自动机(?)+高精+普及dp. 这个是猫老师的序列自动机(字符串从1开始): ]) { memset(t[n],-,sizeof(t[n])); ;i> ...

  7. 【NOIP提高A组模拟2018.8.14】 区间

    区间加:差分数组修改 O(n)扫描,负数位置单调不减 #include<iostream> #include<cstring> #include<cstdio> # ...

  8. JDBC-防止SQL注入问题

      String sql = "select * from user where name = '" + name + "' and password = '" ...

  9. 单行代码实现xml转换成数组

    $string = '<xml> <return_code><![CDATA[SUCCESS]]></return_code> <return_m ...

  10. python基本操作(四)

    与用户交互 为什么交互? 计算机取代人类,解放劳动力 如何交互 print('-'*100) input('请输入你的姓名:') print(""100) Python2和Pyth ...