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

首先摆上一张图片:

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

以最基本的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. Activity详解四 activity四种加载模式

    先看效果图: 1概述 Activity启动方式有四种,分别是: standard singleTop singleTask singleInstance 可以根据实际的需求为Activity设置对应的 ...

  2. Android Gradle Build Error:Some file crunching failed, see logs for details解决办法

    转载请标明出处: http://www.cnblogs.com/why168888/p/5925756.html 本文出自:[Edwin博客园] 错误日志:Error:java.lang.Runtim ...

  3. Macbook SSD硬盘空间不够用了?来个Xcode大瘦身吧!

    原文转自:http://www.jianshu.com/p/03fed9a5fc63    日期:2016-04-22 最近突然发现我的128G SSD硬盘只剩下可怜的8G多,剩下这么少的一点空间连X ...

  4. IOS RunLoop浅析 一

    RunLoop犹如其名循环. RunLoop 中有多重模式. 在一个“时刻”只能值执行一种模式. 因此在使用RunLoop时要注意所实现的效果有可能不是你想要的. 在这里用NSTimer展示一下Run ...

  5. SQL SERVER 监控数据文件增长情况

    在项目前期评估数据库的增长情况,然后根据数据库数据量的增长情况来规划存储的分配其实是一件比较麻烦的事情.因为项目没有上线,用什么来评估数据库的数据增长情况呢? 如果手头没有实际的数据,我们只能从表的数 ...

  6. ORA-01336: specified dictionary file cannot be opened

    这篇介绍使用Logminer时遇到ORA-01336: specified dictionary file cannot be opened错误的各种场景 1:dictionary_location参 ...

  7. 聊下git pull --rebase

    有一种场景是经常发生的. 大家都基于develop拉出分支进行并行开发,这里的分支可能是多到数十个.然后彼此在进行自己的逻辑编写,时间可能需要几天或者几周.在这期间你可能需要时不时的需要pull下远程 ...

  8. SQL*LOADER错误总结

    在使用SQL*LOADER装载数据时,由于平面文件的多样化和数据格式问题总会遇到形形色色的一些小问题,下面是工作中累积.整理记录的遇到的一些形形色色错误.希望能对大家有些用处.(今天突然看到自己以前整 ...

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

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

  10. JS入门学习,写一个简单的选项卡

    /* 经过昨天一整天的纠结和摸索.总结下学习初期我最致命的几个问题…… 1.var oDiv = document.getElementById('');    一定要多输,熟悉后o u什么的字母别搞 ...