以容器类为例子,可以观一叶而知秋,看看以前的前辈们是如何处理各种面向对象思想下的继承体系的。
读的源代码越多,就越要总结这个继承关系否则读的多也忘得快。

首先摆上一张图片:

看到这张图很多人就慌了,难道这些东西我都要全部学习?其实是也不是,其中的很多东西都很有学习的必要,但是学习的过程绝对不是一行一行背诵,每一个类有哪些方法。而怎么从大体上掌握这个继承体系呢?

以最基本的ArrayList<E>为例子。(JDK 1.8环境下)

从ArrayList<E>开始往上看继承体系:

ArrayList(C)--->AbstractList(A)--->AbstractCollection(A)--->Collection(I)--->Iterable(I)

|--->List(I)--->Collection(I)

首先这个继承体系有两条线,我们从上往下看

Iterable,也就是迭代器,是个接口。任何类如果实现了这个接口,就必须实现一个函数,这个函数返回一个迭代器对象。

从某种意义上来讲,这个接口不属于这个继承体系,因为这个接口的主要目的是让实现它的类带有一种功能:通过迭代器访问。因为Collection继承了这个接口,可以想到的是肯定每一个容器都能通过迭代器访问,那么为什么要这么设计呢?

一开始接触的容器比较简单,能通过某种方式遍历,比如通过一个Index来遍历。可是对于一些容器,没有明显的某个数值(或者索引或者下标)可以遍历。

这个问题再进一步就是这样:容器的底层实现各不相同,为了简化遍历这个操作,就有了迭代器,实现屏蔽其底层数据结构的遍历。你用别人写的容器的时候不需要知道它怎么实现的,就只需要拿到这个迭代器,然后遍历就可以了。容器的提供者(编写者)负责这个容器能拿出一个正确的迭代器。(通过实现Iterable接口)

看到这里就能明白,迭代器接口不是单单针对容器,假如你写一个让别人能按照你想法遍历的类,都可以用迭代器。

接下来,我们看看Collection类。这个类是一个接口,里面写了很多方法。除去继承自Object类的方法,和容器有关的主要有:add(),remove(),contains(),isEmpty(),iterator(),size(),toArray()。这里没有写完,也不需要写完。主要就是要明白,一般顶层的接口会有一个极其言简意赅的名字(Collection,List,Set,Executor等等),然后在里面提供最最最基本的操作。比如像这些操作,我想只要是个容器就应该提供吧。

接下来是一个中间层:AbstractCollection。这个抽象类是拿来干嘛的呢?其实一句话表述,它能尽可能实现那些可以实现的操作。这就有点不明白了,你说你上面是个接口,可以说啥都没有,你既然不知道底层的数据结构(使用数组实现的?还是链表?还是???),能在这个层面上实现什么呢?

其实不然,虽然现阶段,抽象类只能看到接口,完全不知道具体怎么实现,可是它知道一定会实现。

具体来说吧,这是AbstractCollection里面的一部分源代码:

     public boolean contains(Object o) {
Iterator<E> it = iterator();
if (o==null) {
while (it.hasNext())
if (it.next()==null)
return true;
} else {
while (it.hasNext())
if (o.equals(it.next()))
return true;
}
return false;
}

它虽然不知道你到底怎么实现了iterator(),但是它知道你一定会实现,因为是抽象方法。它在这个层面就直接拿来用了。而通过一个Iterator变量一个容器的代码,谁写基本都是这样。

总结来说,就是位置在继承体系中间的抽象类(如AbstractCollection)它主要作用是封装那些这个层面可以基本实现的差不多的代码。

再然后是AbstractList。这个抽象类类继承了AbstractCollection,同时实现了List接口。

既然前面已经通过抽象类封装了基本方法了,那么这个抽象类又是拿来干嘛的呢?很简单,之前的都是从一个Collection型的容器,功能特别单一,从这里开始就要进行分流了,很明显这个抽象类和它再往下的继承体系都是往List方向发展。

这里的List是个接口,继承自Collection接口,最主要是多了如下几个方法:

get(index),indexOf(Object),listIterator(),set(index,Object);

这说明一个什么?也就是说如果Collection是最基本的容器的话,List就是容器之中的线性表。可以类比数组。因为它的线性表特性,它的数据底层是通过首地址和偏移量的形式储存的,在这个层面上可以认为它是通过一个index(下标)对于上容器里面的元素的。所以对这个借口对应的容器来说,就不止是简单的添加删除了,它还可以做到在某个特定位置添加,修改,或者通过下标取得某个位置的值。

这里可以用简单的话来阐述我理解的这个地方的继承关系以及为什么要这么设计。

List继承自Collection说明它是一种容器,不过它不只单单是容器,它还有线性表性质。也就是说它跟高级了,从容器里面分离出来了一些。

而AbstractList为什么要继承List呢?它是在AbstractCollection基础上继承的List。也就是说,它是一个容器,可是它通过继承List和别的容器区分开来(如AbstractSet)。所以从这里看的话,List在继承体系中至少有这两个作用,1,分化继承线路,2,以后拿来给容器向上转型。

同时AbstractList还有一段有趣的代码:

     private class Itr implements Iterator<E> {

         int cursor = 0;

         int lastRet = -1;

         int expectedModCount = modCount;

         public boolean hasNext() {
return cursor != size();
} public E next() {
checkForComodification();
try {
int i = cursor;
E next = get(i);
lastRet = i;
cursor = i + 1;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}

这段代码是AbstractList里面的迭代器的代码的节选。问题的核心是:get()方法根本没有:

 abstract public E get(int index);

这还是利用了上面提到的思想:在这个层面还是完全不知道底层的真正数据结构和基本操作的真正实现方式。那么你的get(),set()函数肯定是写不出来的。但是在这个层面它为了尽可能地实现一些大同小异的代码,比如这里的Itr的实现,它既然认定这个抽象方法一定会被继承者实现,它就直接拿来用了。直接利用没有实现的get()方法来实现迭代器。这个过程真是和踢皮球一样:上层的AbstractCollection认为下层会实现迭代器,于是直接使用迭代器写出了Contains()方法。这一层认为下一层应该实现get()方法,于是它就直接用get()方法实现了迭代器。最后的结果是好的,最后一层理论上只需要关注自己切切实实的get(),set()等等方法。

AbstractList再往下就是Arraylist了。这就不用说了,底层的最终实现。封装的工具类,一般来说不需要再继承它只需要使用它。它不再是一个抽象类,里面很多方法得到了实现。应为它最终添加了底层的数据结构实现。所以对它来说一切都是可以明白的,到底那些方法怎么实现。

这都只是Arraylist这条路的一路下来的东西,很多别的继承结构都是基于这个体系的。

从基层容器类看万变不离其宗的JAVA继承体系的更多相关文章

  1. 关于Java继承体系中this的表示关系

    Java的继承体系中,因为有重写的概念,所以说this在子父类之间的调用到底是谁的方法,或者成员属性,的问题是一个值得思考的问题; 先说结论:如果在测试类中调用的是子父类同名的成员属性,这个this. ...

  2. Java继承体系中this的表示关系

    在继承关系下,父类中的this关键字并不总是表示父类中的变量和方法.this关键字的四种用法如前文所述,列举如下. 1) this(paras…); 访问其他的构造方法 2) this.xxx; 访问 ...

  3. RPC 核心,万变不离其宗

    微信搜 「yes的练级攻略」干货满满,不然来掐我,回复[123]一份20W字的算法刷题笔记等你来领. 个人文章汇总:https://github.com/yessimida/yes 欢迎 star ! ...

  4. 万变不离其宗之UART要点总结

    [导读] 单片机开发串口是应用最为广泛的通信接口,也是最为简单的通信接口之一,但是其中的一些要点你是否明了呢?来看看本人对串口的一些总结,当然这个总结并不能面面俱到,只是将个人认为具有共性以及相对比较 ...

  5. 深入super,看Python如何解决钻石继承难题 【转】

    原文地址 http://www.cnblogs.com/testview/p/4651198.html 1.   Python的继承以及调用父类成员 python子类调用父类成员有2种方法,分别是普通 ...

  6. java继承内部类问题(java编程思想笔记)

    普通内部类默认持有指向所属外部类的引用.如果新定义一个类来继承内部类,那“秘密”的引用该如何初始化? java提供了特殊的语法: class Egg2 { public class Yolk{ pub ...

  7. Java 继承详解

    什么是继承? 多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可. 多个类可以称为子类,单独这个类称为父类.超类或者基类. 子类可以直接 ...

  8. 深入super,看Python如何解决钻石继承难题

    1.   Python的继承以及调用父类成员 python子类调用父类成员有2种方法,分别是普通方法和super方法 假设Base是基类 class Base(object): def __init_ ...

  9. JAVA 继承中的this和super

    学习java时看了不少尚学堂马士兵的视频,还是挺喜欢马士兵的讲课步骤的,二话不说,先做实例,看到的结果才是最实际的,理论神马的全是浮云.只有在实际操作过程中体会理论,在实际操作过程中升华理论才是最关键 ...

随机推荐

  1. Play Framework 完整实现一个APP(九)

    添加增删改查操作 1.开启CRUD Module 在/conf/application.conf 中添加 # Import the crud module module.crud=${play.pat ...

  2. yii2分页的基本使用及其配置详解

    作者:白狼 出处:http://www.manks.top/yii2_linkpager_pagination.html 本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置 ...

  3. Linux如何查看JDK的安装路径

    如何在一台Linux服务器上查找JDK的安装路径呢? 有那些方法可以查找定位JDK的安装路径?是否有一些局限性呢? 下面总结了一下如何查找JDK安装路径的方法.   1:echo $JAVA_HOME ...

  4. SQL SERVER 中如何用脚本管理作业

    在SQL SERVER中用脚本管理作业,在绝大部分场景下,脚本都比UI界面管理作业要高效.简洁.打个简单的比方,如果你要查看作业的运行时长,如果用UI界面查看,100个作业,你就得在历史记录里面至少查 ...

  5. hibernate 注解 唯一键约束 uniqueConstraints

    @Table 注解包含一个schema和一个catelog 属性,使用@UniqueConstraints 可以定义表的唯一约束. 如果是联合约束就用下面这种 @Table(name="tb ...

  6. 配置windows路由表,使电脑同时连接内网外网方法

    1.环境一(系统:windows xp,内网.外网不是同一类地址,内网地址固定): 外网:通过笔记本的无线网卡连接: 内网:通过笔记本的本地连接: 第一步,连接网线,配置本地连接地址,注意IP地址不要 ...

  7. Java程序生成exe可执行文件详细教程(图文说明)

    ava程序打包成exe可执行文件,分为两大步骤. 第一步:将Java程序通过Eclipse或者Myeclipse导成Jar包 第二步:通过exe4j讲Jar包程序生成exe可执行文件 第一步详解: 将 ...

  8. SDN:motivation

    今天公交车上看了会SDN一本介绍性的书籍,具体名字不记得了.我想,我已经在实验室呆了很久的时间的,接触SDN也有一段时间了.对SDN的一些基本的知识还是需要好好整理一番.当然,这里只是一个随笔,想到什 ...

  9. 【bzoj1010】 HNOI2008—玩具装箱toy

    http://www.lydsy.com/JudgeOnline/problem.php?id=1010 (题目链接) 题意 给定N个物品,可以连续的划分为若干个组,每个组的代价是(物品数-1+每个物 ...

  10. etl实现字段值相加

    数据库USERS表: etl步骤: (2) (3) 其中java代码为: import test.Test;          public boolean processRow(StepMetaIn ...