点击查看 Java 集合框架深入理解 系列, - ( ゜- ゜)つロ 乾杯~


今天心情和股票一样红,还是学学 ListIterator 吧!

ListIterator

根据官方文档介绍, ListIterator 有以下功能:

  1. 允许我们向前、向后两个方向遍历 List;
  2. 在遍历时修改 List 的元素;
  3. 遍历时获取迭代器当前游标所在位置。

注意,迭代器 没有当前所在元素一说,它只有一个游标( cursor )的概念,这个游标总是在元素之间,比如这样:

初始时它在第 0 个元素之前,调用 next() 游标后移一位:

调用 previous() 游标就会回到之前位置。当向后遍历完元素,游标就会在元素 N 的后面:

也就是说长度为 N 的集合会有 N+1 个游标的位置。


ListIterator 继承自 Iterator 接口(关于 Iterator 的介绍 请点这里),在 Iterator 的基础上增加了 6 个方法:

介绍一下新来的几个方法:

  • void hasPrevious()

    • 判断游标前面是否有元素;
  • Object previous()
    • 返回游标前面的元素,同时游标前移一位。游标前没有元素就报 java.util.NoSuchElementException 的错,所以使用前最好判断一下;
  • int nextIndex()
    • 返回游标后边元素的索引位置,初始为 0 ;遍历 N 个元素结束时为 N;
  • int previousIndex()
    • 返回游标前面元素的位置,初始时为 -1,同时报 java.util.NoSuchElementException 错;
  • void add(E)
    • 在游标 前面 插入一个元素
    • 注意,是前面
  • void set(E)
    • 更新迭代器最后一次操作的元素为 E,也就是更新最后一次调用 next() 或者 previous() 返回的元素。
    • 注意,当没有迭代,也就是没有调用 next() 或者 previous() 直接调用 set 时会报 java.lang.IllegalStateException 错;
  • void remove()
    • 删除迭代器最后一次操作的元素,注意事项和 set 一样。

ListIterator 有两种获取方式

  • List.listIterator()
  • List.listIterator(int location)

区别在于第二种可以指定 游标的所在位置。

ListIterator 的具体实现?

AbstractList 作为 List 的直接子类,里面实现了 listIterator() 方法,并且有两个内部迭代器实现类:SimpleListIterator,FullListIterator:

listIterator() 返回的是 FullListIterator():

FullListIterator 继承了 SimpleListIterator, SimpleListIterator 实现了 Iterator 接口:

private class SimpleListIterator implements Iterator<E> {
//游标的位置,初始为 -1
int pos = -1;
//用来判断是否 fail-fast 的变量
int expectedModCount;
//记录上次迭代的位置
int lastPosition = -1; SimpleListIterator() {
expectedModCount = modCount;
} //当游标没有跑到最后一个元素后面时 hasNext 返回 true
public boolean hasNext() {
return pos + 1 < size();
} //获取下一个元素
public E next() {
if (expectedModCount == modCount) {
try {
//获取游标后面的元素,具体子类有具体实现
E result = get(pos + 1);
//更新
lastPosition = ++pos;
return result;
} catch (IndexOutOfBoundsException e) {
throw new NoSuchElementException();
}
}
//当迭代时修改元素,就会报这个错,上篇文章介绍过解决办法~
throw new ConcurrentModificationException();
} //删除上次迭代操作的元素
public void remove() {
//还没进行迭代操作就会报这个错
if (this.lastPosition == -1) {
throw new IllegalStateException();
} if (expectedModCount != modCount) {
throw new ConcurrentModificationException();
} try {
//调用子类实现的删除操作
AbstractList.this.remove(lastPosition);
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
} expectedModCount = modCount;
if (pos == lastPosition) {
pos--;
}
//每次删除后都会还原为 -1,也就是说我们迭代一次后只能 remove 一次,再 remove 就会报错
lastPosition = -1;
}
}

了解了 SimpleListIterator 后我们看下 FullListIterator 的具体实现:

private final class FullListIterator extends SimpleListIterator implements ListIterator<E> {
//根据 start 指定游标位置
FullListIterator(int start) {
if (start >= 0 && start <= size()) {
pos = start - 1;
} else {
throw new IndexOutOfBoundsException();
}
} //在游标前面添加元素
public void add(E object) {
if (expectedModCount == modCount) {
try {
//调用子类的添加操作,ArrayList, LinkedList,Vector 的添加操作实现有所不同
AbstractList.this.add(pos + 1, object);
} catch (IndexOutOfBoundsException e) {
throw new NoSuchElementException();
}
//游标后移一位
pos++;
//!注意! 添加后 上次迭代位置又变回 -1 了,说明 add 后调用 remove, set 会有问题!
lastPosition = -1;
if (modCount != expectedModCount) {
expectedModCount = modCount;
}
} else {
throw new ConcurrentModificationException();
}
} //当游标不在初始位置(-1)时返回true
public boolean hasPrevious() {
return pos >= 0;
} //游标后面的元素索引,就是游标 +1
public int nextIndex() {
return pos + 1;
} //游标前面一个元素
public E previous() {
if (expectedModCount == modCount) {
try {
E result = get(pos);
lastPosition = pos;
pos--;
return result;
} catch (IndexOutOfBoundsException e) {
throw new NoSuchElementException();
}
}
throw new ConcurrentModificationException();
} //游标前面元素的索引,就是游标的位置,有点晕的看开头那几张图
public int previousIndex() {
return pos;
} //更新之前迭代的元素为 object
public void set(E object) {
if (expectedModCount == modCount) {
try {
//调用子类的set
AbstractList.this.set(lastPosition, object);
} catch (IndexOutOfBoundsException e) {
throw new IllegalStateException();
}
} else {
throw new ConcurrentModificationException();
}
}
}

可以看到 SimpleListIterator 的主要操作最后都交给子类来实现,List 的子类 ArrayList, LinkedList, Vector 由于底层实现原理不同(数组,双向链表),具体操作类实现有所不同。

等接下来分析到具体子类再看相关实现吧。

Java 集合源码解析(2):ListIterator的更多相关文章

  1. java集合 源码解析 学习手册

    学习路线: http://www.cnblogs.com/skywang12345/ 总结 1 总体框架 2 Collection架构 3 ArrayList详细介绍(源码解析)和使用示例 4 fai ...

  2. Java 集合源码解析(1):Iterator

    Java, Android 开发也有段时间了,当初为了早点学 Android,Java 匆匆了解个大概就结束了,基础不够扎实. 虽然集合框架经常用,但是一直没有仔细看看原理,仅止于会用,不知道为什么要 ...

  3. java集合源码分析几篇文章

    java集合源码解析https://blog.csdn.net/ns_code/article/category/2362915

  4. Java集合源码分析(三)LinkedList

    LinkedList简介 LinkedList是基于双向循环链表(从源码中可以很容易看出)实现的,除了可以当做链表来操作外,它还可以当做栈.队列和双端队列来使用. LinkedList同样是非线程安全 ...

  5. 【转】Java HashMap 源码解析(好文章)

    ­ .fluid-width-video-wrapper { width: 100%; position: relative; padding: 0; } .fluid-width-video-wra ...

  6. Java集合类源码解析:Vector

    [学习笔记]转载 Java集合类源码解析:Vector   引言 之前的文章我们学习了一个集合类 ArrayList,今天讲它的一个兄弟 Vector.为什么说是它兄弟呢?因为从容器的构造来说,Vec ...

  7. java集合源码分析(三):ArrayList

    概述 在前文:java集合源码分析(二):List与AbstractList 和 java集合源码分析(一):Collection 与 AbstractCollection 中,我们大致了解了从 Co ...

  8. Java集合源码分析(四)Vector<E>

    Vector<E>简介 Vector也是基于数组实现的,是一个动态数组,其容量能自动增长. Vector是JDK1.0引入了,它的很多实现方法都加入了同步语句,因此是线程安全的(其实也只是 ...

  9. Java集合源码分析(二)ArrayList

    ArrayList简介 ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存. ArrayList不是线程安全的,只能用在单线程环境下,多线 ...

随机推荐

  1. javasE学习笔记:关键字super的使用

    /* super 的作用: 1 . super可以在子类中引用父类的成员,通过 .的方式和属性.this相对应. 2. 在子类的构造方法中可以可使用super(参数列表)语句调用父类的构造方法 3. ...

  2. java 多线程——一个定时调度的例子

    java 多线程 目录: Java 多线程——基础知识 Java 多线程 —— synchronized关键字 java 多线程——一个定时调度的例子 java 多线程——quartz 定时调度的例子 ...

  3. struts2视频学习笔记 29-30(Struts 2常用标签,防止表单重复提交)

    课时28 Struts 2常用标签解说 property标签 property标签用于输出指定值: <s:set name="name" value="'kk'&q ...

  4. Bag of mice(CodeForces 148D )

    D. Bag of mice time limit per test 2 seconds memory limit per test 256 megabytes input standard inpu ...

  5. 241. Different Ways to Add Parentheses——本质:DFS

    Given a string of numbers and operators, return all possible results from computing all the differen ...

  6. Ubuntu 下Eclipse 安装SVN

    如果尚未安装Eclipse,先安装:也可以直接下载Google提供的ADT Bundle. sudo apt-get install eclipse 安装Subversion sudo apt-get ...

  7. mysql在一台服务器搭建主从1

    1. 登录mysq的方法:  mysql-S /tmp/mysql.sock 登录3306  mysql -S /tmp/mysql_slave.sock 登录3307 mysql -h 127.0. ...

  8. mysql sql 百万级数据库优化方案

    1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引. 2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索 ...

  9. sqlserver 2008 左补齐字符串

    SQLServer:right函数 语法 Right(string, length)   Right 函数的语法具有下面的命名参数:   部分 说明 string 必要参数.字符串表达式,从中最右边的 ...

  10. getWritableDatabase()与getReadableDatabase()方法

    一旦在程序中得到了SQLiteOpenHelper对象之后,程序无须使用SQLiteDatabase的静态方法创建SQLiteDatabase实例,而且可以使用getWritableDatabase( ...