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. jsp另外五大内置对象之response-操作cookie

    responseo3.jsp <%@ page language="java" contentType="text/html; charset=utf-8" ...

  2. Eclipse中添加MyEclipse插件

    下载准备: 下载myeclipse7.0:http://downloads.myeclipseide.com/downloads/products/eworkbench/7.0M1/MyEclipse ...

  3. python3从尾到头打印链表

    题目描述 输入一个链表,按链表值从尾到头的顺序返回一个ArrayList. 方法一:通过栈实现 # -*- coding:utf-8 -*- # class ListNode: # def __ini ...

  4. 超全面Java 面试题(2.1)

    这部分主要是开源JavaEE框架方面的内容,包括hibernate.MyBatis.spring.Spring MVC等,由于Struts2已经是明日黄花,在这里就不讨论Struts2的面试题,此外, ...

  5. 主成分分析法(PCA)答疑

    问:为什么要去均值? 1.我认为归一化的表述并不太准确,按统计的一般说法,叫标准化.数据的标准化过程是减去均值并除以标准差.而归一化仅包含除以标准差的意思或者类似做法.2.做标准化的原因是:减去均值等 ...

  6. 66. Plus One@python

    Given a non-empty array of digits representing a non-negative integer, plus one to the integer. The ...

  7. 【模拟】HHHOJ#251. 「NOIP模拟赛 伍」高精度

    积累模拟经验 题目描述 维护一个二进制数,支持如下操作 "+" 该数加 11 "-" 该数减 11 "*" 该数乘 22 "\&q ...

  8. Linux中的常见命令

    1.   ls    查看当前目录下的所有文件夹 2.   pwd  查看当前所在的文件夹 3. cd 目录名 切换文件夹 4. touch 文件名 创建文件 5. mkdir 目录名 创建文件夹 6 ...

  9. perl-tips-1

    .pm 应该保存 Perl Module,也就是 Perl 模块.例如 Socket.pm.pl 应该保存 Perl Library,也就是 Perl 库文件.例如 perldb.pl.plx 应该保 ...

  10. python之序列化

    什么叫序列化? 序列化是指把内存里的数据类型转变成字符串,以使其能存储到硬盘或通过网络传输到远程,因为硬盘或网络传输时只能接受bytes. 把字符转换成内存数据类型,叫反序列化. 为什么要序列化? 你 ...