一、前言

  前面我们介绍了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. vmware 无法打开内核设备 \\.\Global\vmx86: 系统找不到指定的文件

    点击“开始→运行”,在运行框中输入 CMD 回车打开命令提示符,然后依次执行以下命令. net start vmci net start vmx86 net start VMnetuserif sc ...

  2. Web服务器性能监控分析与优化

    Web服务器性能监控分析与优化 http://www.docin.com/p-759040698.html

  3. 无法打开包括文件:'atlrx.h'的解决办法

    VS 2008中由于将ALT项目的部分代码剥离出去成为了独立的开源项目,需要用到ALT中正则表达式等功能就需要手动下载. 我不是第一个遇到这个问题的,所以已经有前人给出了解决方案. 可到http:// ...

  4. Unity数据存储路径总结

    一.在项目根目录中创建Resources文件夹来保存文件.可以使用Resources.Load("文件名字,注:不包括文件后缀名");把文件夹中的对象加载出来.注:此方可实现对文件 ...

  5. Android-NDK编译:cocos2d-x(三) eclipse 导入工程

    NDK 编译后,用eclipse导入cocos2d-x工程 菜单[File]-->[New]-->[Project] ,弹出New Project 对话框 窗口下方 选 [Android] ...

  6. React Native ——实现一个简单的抓取github上的项目数据列表

    /** * Sample React Native App * https://github.com/facebook/react-native */ 'use strict'; var React ...

  7. ODAC(V9.5.15) 学习笔记(十八) 数据集缓冲模式

    数据集的缓冲模式(Cached mode)是将数据库服务器的数据缓冲在客户端内存中进行处理,不再依赖服务器.只有当数据需要提交数据库服务器进行保存时,才将变更数据一次性提交数据库服务器. 数据集缓冲模 ...

  8. ubuntu 修改默认root及密码

    安装完Ubuntu后忽然意识到没有设 置root密码,不知道密码自然就无法进入根用户下.到网上搜了一下,原来是这麽回事.Ubuntu的默认root密码是随机的,即每次开机都有一个新的 root密码.我 ...

  9. U3D中IOS平台泛型方法尽少使用

    U3D的IOS最小运行库use micro mscorlib是不包含泛型反射方法的,如FieldType.GetGenericArguments方法.所以尽量少用List而直接使用array. 另外请 ...

  10. linux 系统运维

    Linux 系统监控1.进程    查看所有进程     ps -ef     ps -ef |grep nginx 结束进程    # 结束进程号为5031    kill -g 5031 # 结束 ...