Java 集合源码解析(2):ListIterator

今天心情和股票一样红,还是学学 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 由于底层实现原理不同(数组,双向链表),具体操作类实现有所不同。

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java开发者杂谈

Java-String.intern的深入研究

When---什么时候需要了解String的intern方法: 面试的时候(蜜汁尴尬)!虽然不想承认,不过面试的时候经常碰到这种高逼格的问题来考察我们是否真正理...

3176
来自专栏Linyb极客之路

浅谈Java中的hashcode方法

哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率。在Java的Object类中有一个方法:

411
来自专栏AILearning

java.util.Date和java.sql.Date的区别及转换

java.util.Date和java.sql.Date的区别及应用 java.util.Date 就是在除了SQL语句的情况下面使用 java.sql....

1709
来自专栏老马说编程

(38) 剖析ArrayList / 计算机程序的思维逻辑

从本节开始,我们探讨Java中的容器类,所谓容器,顾名思义就是容纳其他数据的,计算机课程中有一门课叫数据结构,可以粗略对应于Java中的容器类,我们不会介绍所有...

2015
来自专栏令仔很忙

新手学JAVA(十)-多线程----线程的创建和启动

通过继承Thread类或实现Runnable、Callable接口都可以实现多线程。不过实现Runnable和实现Callable的方式基本相同,只不过Cal...

691
来自专栏芋道源码1024

ArrayList 其实也有双胞胎,但区别还是挺大的!

这句话的意思是Collection集合类型的toArray()方法虽然声明返回值类型是Object[],但是具体调用时还真不一定就返回Onject[]类型,也有...

814
来自专栏待你如初见

Day10

1322
来自专栏Java技术栈

Java码农必须掌握的循环删除List元素的正确方法!

首先看下下面的各种删除list元素的例子 public static void main(String[] args) { List<Stri...

33510
来自专栏Java成神之路

Java_数据交换_dom4j_01_解析xml

792
来自专栏微信公众号:Java团长

浅谈Java中的hashcode方法

  哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率。在Java的Object类中有一个方法:

511

扫码关注云+社区