一、前言

  前面我们介绍了Collection及其抽象实现,在JAVA的容器体系里,由Collection派生出来的有两大体系,即List和Map。本文以及后续文章将重点分析List体系。本文将重点分析List接口定义及其抽象实现AbstractList.

二、List接口定义

还是先来看一下JDK对于List的定义:An ordered collection (also known as a sequence). The user of this interface has precise control over where in the list each element is inserted. The user can access elements by their integer index (position in the list), and search for elements in the list.

通过上面的定义我们知道,list是一个有序的collection,可以通过index进行精确的插入,访问和查找。相比于collection来说,list接口的定义增加了通过index的一系列操作。List接口新增的方法如下 :

1. 添加: 有add(int index, E e), addAll(int index, E e)

2. 修改: set(int index, E e)

3. 删除:remove(index)

4. 查找: indexOf(int index), lastIndexOf(int index), get(int index)

5. 遍历:listIterator(), listIterator(int index), subList(int fromIndex, int toIndex)

可以看到新增的方法提供了更精确的根据index来对列表进行操作,充分体现了list的特点,另外提供了更强大的遍历方法,即listIterator和subList.

三、AbstractList 介绍

AbstractList 继承自AbstractCollection,同时实现了List接口,提供了很多方法的默认实现,但这仍然是一个抽象类,我们可能通过继承该类,来快速地实现一个List. 对AbstractList的特点如下:

1.  所有的add方法都会转化为调用add(index, E e), 而方法默认是不被支持的

2.  同样不被支持的还有remove(int index),和set(int index, E e)

3.  get(int index)是一个惟一的抽象方法,没有实现。

4.  iterator方法不再是抽象的,将返回一个内部类实现

5.  size() 方法仍然是抽象的

从上面的特点我们可以看到,AbstractList并没有定义如果通过下标来操作list,这是因为不同的具体list类的数据结构不一样,访问方式也就不一样,而即使不实现,也不影响对于整个list的遍历,而如何获取某个位置的元素取决于get(index),所以这个方法是抽象的。

四、关键方法介绍

新增的这几个方法还是比较好理解的,我们重点对iterator和subList的实现原理进行介绍。

1. iterator() 方法很简单,直接返回了一个新的Itr实例: return new Itr();该类的源码如下:

 private class Itr implements Iterator<E> {
/**
* Index of element to be returned by subsequent call to next.
*/
int cursor = 0; /**
* Index of element returned by most recent call to next or
* previous. Reset to -1 if this element is deleted by a call
* to remove.
*/
int lastRet = -1; /**
* The modCount value that the iterator believes that the backing
* List should have. If this expectation is violated, the iterator
* has detected concurrent modification.
*/
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();
}
} public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification(); try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
} final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}

  通过分析源码,我们可以看到这是一个内部类,完整的实现了iterator接口,定义了两个变量cursor及lastRet,cursor代表了迭代器中的下一个元素的位置,初始时为0,即初始时,下一个要遍历的元素的下标是0,lastRet表示刚刚遍历后的元素的下标,默认为-1。其hasNext方法是判断cursor是否为size(), 这个表示遍历结束,可以多次调用而不影响迭代元素的位置。

对于next()来说,其主要逻辑是返回当前元素,并更新cursor为下一个元素的下标,更新lastRet为当前元素的下标,这个元素也是刚被遍历过的。所以,对于next()方法来说,也可以多次调用,只是在执行逻辑时,next()方法会先检查下标的范围是否越界,如果越界会抛出异常。

而对于remove()方法来说,每执行一次,lastRet的值会变成-1, cursor的值会减1,而remove方法调用前会先判断lastRet的值是否小于0,如果小于0则会抛出异常,所以这个remove()方法只能被调用一次,必须再调用一次next()方法后才能再调用remove().

对于remove()和next()来说,最终的实现还是依赖于AbstractList中的remove和get方法,所以其行为还是由list来决定的。

2. listIterator()方法返回一个ListItr对象,这个实现如下:

 private class ListItr extends Itr implements ListIterator<E> {
ListItr(int index) {
cursor = index;
} public boolean hasPrevious() {
return cursor != 0;
} public E previous() {
checkForComodification();
try {
int i = cursor - 1;
E previous = get(i);
lastRet = cursor = i;
return previous;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
} public int nextIndex() {
return cursor;
} public int previousIndex() {
return cursor-1;
} public void set(E e) {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification(); try {
AbstractList.this.set(lastRet, e);
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
} public void add(E e) {
checkForComodification(); try {
int i = cursor;
AbstractList.this.add(i, e);
lastRet = -1;
cursor = i + 1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}

其继承自Itr,实现了ListIterator,相比于Iterator只能单向遍历来说,ListIterator可以双向遍历,并且提供了add和set的方法,功能更为强大。对于无参数的listIterator来说,cursor的值为0 ,而带参数的方法指定了cursor的值。

和remove()一样,当调用add()方法时,lastRet的值会变为-1,那这样就不能执行set和和remove了,而add方法实际上是可以执行多次的。另外add和set最终还是依赖于list的实现。

其它新增的方法和原来的类似,只是说可以从另一端进行遍历。

3. subList(beginIndex, endIndex) 返回了一个SubList对象。

SubList实现了List的全部接口,但其对于list中各种方法的操作却依赖于构造函数中传递过来的list对象。所以,可以把SubList理解为是一个适配器。它和普通的列表并无两样,只是列表的开始位置不再是0,而是beginIndex, 列表的长度也不再是size(),而是endIndex - beginIndex.

  另外SubList并不是复制出一原列表的一部分,而是可以看成是原列表的一个视图,对这个子列表的所有修改,也就相当于是对原列表进修改,所以在做更新操作时,需要注意到这一点。

最后需要注意的一点,就是subList是一个左闭右开的集合,比如list.subList(0,1)的子集合长度为1,其中包括元素list.get(0),不包括list.get(1)

五、总结

总的来说,List把Collection更具体化了,在加了一些限制的同时,也增加了更多的功能,主要就是按索引对元素进行操作。后续我们会围绕着List进行更深入的介绍。

容器---List和AbstractList的更多相关文章

  1. Java容器解析系列(3) List AbstractList ListIterator RandomAccess fail-fast机制 详解

    做为数据结构学习的常规,肯定是先学习线性表,也就是Java中的List,开始 Java中List相关的类关系图如下: 此篇作为对Java中相关类的开篇.从上图中可以看出,List和AbstractLi ...

  2. Java入门记(四):容器关系的梳理(上)——Collection

    目录 一.Collection及子类/接口容器继承关系 二.List 2.1 ArrayList 2.1.1 序列化的探讨 2.1.2 删除元素 2.1.3 调整大小 2.2 Vector和Stack ...

  3. 转:java多线程--同步容器

    java同步容器 在Java的集合容器框架中,主要有四大类别:List.Set.Queue.Map.List.Set.Queue接口分别继承了Collection接口,Map本身是一个接口.注意Col ...

  4. 给jdk写注释系列之jdk1.6容器(10)-Stack&Vector源码解析

    前面我们已经接触过几种数据结构了,有数组.链表.Hash表.红黑树(二叉查询树),今天再来看另外一种数据结构:栈.      什么是栈呢,我就不找它具体的定义了,直接举个例子,栈就相当于一个很窄的木桶 ...

  5. 给jdk写注释系列之jdk1.6容器(3)-Iterator设计模式

    前面讲了两种List,一种基于数组实现的ArrayList,一种基于链表实现的LinkedList,这两种list是我们工作中最常用到的List容器.当然数组和链表也是两种常见的基本数据结构,其他基本 ...

  6. 给jdk写注释系列之jdk1.6容器(2)-LinkedList源码解析

    LinkedList是基于链表结构的一种List,在分析LinkedList源码前有必要对链表结构进行说明.   1.链表的概念      链表是由一系列非连续的节点组成的存储结构,简单分下类的话,链 ...

  7. 给jdk写注释系列之jdk1.6容器(1)-ArrayList源码解析

    工作中经常听到别人讲“容器”,各种各样的容器,话说到底什么是容器,通俗的讲“容器就是用来装东西的器皿,比如:水桶就是用来盛水的,水桶就是一个容器.” ok,在我们写程序的时候常常要对大量的对象进行管理 ...

  8. JDK 高性能编程之容器

    高性能编程在对不同场景下对于容器的选择有着非常苛刻的条件,这里记录下前人总结的经验,并对源码进行调试 JDK高性能编程之容器 读书笔记内容部分来源书籍深入理解JVM.互联网等 先放一个类图util,点 ...

  9. Java容器源码解析之——ArrayList

    public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess ...

随机推荐

  1. 关于typedef的使用方法

    在计算机编程语言中用来为复杂的声明定义简单的别名.与宏定义有些差异.它本身是一种存储类的keyword,与auto.extern.mutable.static.register等keyword不能出如 ...

  2. win10上安装Docker

    方法1:具体我没有试过,不知道win10下可以么.http://blog.csdn.net/zistxym/article/details/42918339 方法2: 先安装VirtualBox(下载 ...

  3. CSS3动画进度条

    CSS3动画进度条   CSS CODE: @-webkit-keyframes move{ 0%{ background-position: 0 0; } 100%{ background-posi ...

  4. delphi7的新生,参与分布式应用开发,调用RESTful API,Json的应用

    前言: 1.公司delphi7开发的传统软件还活得好好的,但是大家都知道delphi早已经日落西山了,现在成了后进.追随者.细细算了已经6.7不用了.新的delphixe7呢,没有时间成本去适应和研究 ...

  5. Query Object--查询对象模式(下)

    回顾 上一篇对模式进行了介绍,并基于ADO.NET进行了实现,虽然现在ORM框架越来越流行,但是很多中小型的公司仍然是使用ADO.NET来进行数据库操作的,随着项目的需求不断增加,业务不断变化,ADO ...

  6. c++ 全局变量初始化的一点总结

    注意:本文所说的全局变量指的是 variables with static storage,措词来自 c++ 的语言标准文档. 什么时候初始化 根据 C++ 标准,全局变量的初始化要在 main 函数 ...

  7. mark:如何使用FileZilla连接虚拟机上的Fedora

    1. 下载FileZilla 2. 在虚拟机上安装SSH,http://linuxconfig.org/how-to-install-start-and-connect-to-ssh-server-o ...

  8. `fw服务端非完整` 工程开发初期的工作

    前面写到了一些关于cocos2dx在开发中的一些模块以及一些解决方法,那些都属于本人的个人简介和个人倾向的解决方案.最近这几天我完善了一下ui解析的部分,当然也只是抽出一点点时间去做的这件事情.我添加 ...

  9. windows下mysql远程访问慢

    在my.conf中加入  skip-name-resolve

  10. [转载]在线文档预览方案-Office Web Apps

    最近在做项目时,要在手机端实现在线文档预览的功能.于是百度了一下实现方案,大致是将文档转换成pdf,然后在通过插件实现预览.这些方案没有具体实现代码,也没有在线预览的地址,再加上项目时间紧迫.只能考虑 ...