【Java集合框架】3 - 6 迭代器源码分析

§3-6 迭代器源码分析

3-6.1 回顾迭代器

Iterator<E> 接口,位于 java.util 包下,表示一个用于集合的迭代器。

迭代器特点

  • 迭代器对象可以由 Collection 调用 iterator 方法返回,默认指向集合的起始位置,其最大特点就是不依赖索引;
  • 迭代器一旦创建成功,则不可再使用 Collection 或其他非迭代器自身的修改集合的方法,否则迭代器遍历时检测到 modCount 意外变化,将会抛出异常 ConcurrentMModificationException
  • 迭代器自身只支持 remove 方法,即只支持删除元素;
  • 使用迭代器遍历集合是遍历集合的优先选择,迭代器专用于集合遍历;
  • 使用迭代器遍历集合时,hasNext 方法应与 next 方法一对一结合配套使用,前者用于检测是否存在更多元素用于返回,后者返回下一元素并移动迭代器;若无更多元素可供返回,调用 next 方法会抛出异常 NoSuchElementException
  • 迭代器并不会复位,当需要再次使用迭代器遍历时,应当使用同样方法获取一个新的迭代器对象;

列表迭代器

  • ListIterator<E>Iterator<E> 的子接口,是专用于列表的迭代器;
  • 其特性同 Iterator 迭代器,通过 List 或其实现类调用 listIterator 返回一个列表迭代器对象;
  • 该迭代器支持 previous, hasPrevious, add, set 方法,可以实现增添、修改、逆向遍历的功能;

迭代器的实现类:由于调用 iteratorlistIterator 方法返回的是一个迭代器对象,其返回值类型都是其对应接口。使用接口的多态,则应当返回其实现类对象。查看源码,在不同实现类中,都定义了迭代器的实现类。

3-6.2 ArrayList 中的迭代器 Iterator

此处以 ArrayList 中的 iterator 方法所返回的 Iterator<E> 实现类对象为例。

ArrayList 中还定义了一个内部类 ListItr,作为 ListIterator<E> 的实现类,继承自 Iterator<E> 在其中的内部实现类。

3-6.2.1 返回迭代器对象

调用 ArrayList 中的 iterator 方法,会返回一个 Iterator<E> 对象,其实现为:

public Iterator<E> iterator() {
    return new Itr();
}

返回的是一个 IteratorArrayList 中定义的一个实现类 Itr,其声明为:

private class Itr implements Iterator<E> {
    int cursor;       // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount;
    
    // prevent creating a synthetic constructor
    Itr() {}
    
    ...
}

其中,cursor 指向下一个返回的元素索引;lastRet 指向上一个返回的元素索引;expectedModCountmodCount 与并发修改异常有关;

iterator 方法中使用无参构造,则迭代器对象默认指向列表起始索引处;

3-6.2.2 判断与输出元素

我们使用 hasNext 方法判断是否存在更多元素,其方法实现为:

public boolean hasNext() {
    return cursor != size;
}

本质上就是在判断,当前光标所处位置与实际存储数据个数的大小关系;

使用 next 方法返回下一个元素并移动迭代器,其方法实现为:

@SuppressWarnings("unchecked")
public E next() {
    checkForComodification();
    int i = cursor;
    if (i >= size)
        throw new NoSuchElementException();
    Object[] elementData = ArrayList.this.elementData;
    if (i >= elementData.length)
        throw new ConcurrentModificationException();
    cursor = i + 1;
    return (E) elementData[lastRet = i];
}

方法首先会调用 checkForComodification 检查并发修改,若无并发修改,则继续向下执行;

然后,方法记录当前光标位置 i = cursor,并获取外部类中所存储的数组;

为保证数组内容不发生变化、索引不越界(无并发修改),使用 i 与数组容量做比较,若无问题,则继续执行;

光标前移,指向下一元素,同时将更新 lastRet(上次返回元素的索引),并将 lastRet 处的元素返回;

至此,方法完成了返回元素、移动光标、记录上一次返回元素的索引的工作,出栈;

3-6.2.3 并发修改异常

在迭代器对象创建成功之时,迭代器会记录此时列表结构性修改次数 modCount

int expectedModCount = modCount;

next, remove 方法所做的第一件事,就是先判断数组内容是否相较于迭代器创建之时,发生了变化:

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

这一过程实际上就是将当前修改次数与迭代器所记录的修改次数做比较,判断二者是否相等;

而通过迭代器对象调用其 remove 方法,会在内部同时更新 modCount,此时则不会抛出并发修改异常,该方法的实现为:

public void remove() {
    if (lastRet < 0)
        throw new IllegalStateException();
    checkForComodification();

    try {
        ArrayList.this.remove(lastRet);
        cursor = lastRet;
        lastRet = -1;
        expectedModCount = modCount;
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}

3-6.3 LinkedList 中的列表迭代器 ListIterator

若通过 LinkedList 调用 iterator 方法,实际上,所返回的迭代器对象是 ListIterator<E> 的一个实现类对象。

3-6.3.1 返回迭代器对象

调用 listIterator 的无参版本,返回一个列表迭代器对象,其方法实现为:

public ListIterator<E> listIterator() {
    return listIterator(0);
}

方法内部调用了另一个有参重载,其实现为:

public ListIterator<E> listIterator(int index) {
    checkPositionIndex(index);
    return new ListItr(index);
}

方法首先会调用 checkPositionIndex,检查传入的索引所在范围是否合法:

private void checkPositionIndex(int index) {
    if (!isPositionIndex(index))
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

private boolean isPositionIndex(int index) {
    return index >= 0 && index <= size;
}

若参数合法,则返回一个列表迭代器对象,该实现类位于 LinkedList 中:

private class ListItr implements listIterator<E> {
    private Node<E> lastReturned;
    private Node<E> next;
    private int nextIndex;
    private int expectedModCount = modCount;
    
    ListItr(int index) {
        // assert isPositionIndex(index);
        next = (index == size) ? null : node(index);
        nextIndex = index;
    }
    
    ...
}

该迭代器默认指向头结点,通过 node 方法定位:

Node<E> node(int index) {
    // assert isElementIndex(index);

    if (index < (size >> 1)) {
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

从这个方法可见,该方法常用于查找结点,且判断该节点与首尾结点的距离从而决定从哪一个节点开始寻找;

对于传入参数 0,返回头结点;

3-6.3.2 判断与输出元素

同样地,使用 hasNext 方法判断是否具有更多元素,其实现为:

public boolean hasNext() {
    return nextIndex < size;
}

本质上就是在判断下一个要返回的元素的索引与链表长度的关系;

使用 next 方法返回下一个元素并移动迭代器,其实现为:

public E next() {
    checkForComodification();
    if (!hasNext())
        throw new NoSuchElementException();

    lastReturned = next;
    next = next.next;
    nextIndex++;
    return lastReturned.item;
}

同样地,先调用 checkForComodification 判断是否发生并发修改,若没有,继续执行;

接着,判断是否具有更多元素,否则抛出异常 NoSuchElementException

若有更多元素,返回下一元素,并更新自此之后上一次返回的元素、下一次返回的元素及其索引;

3-6.3.3 并发修改异常

ArrayList,迭代器对象在创建之初就会记录此时列表的结构性修改次数:

int expectedModCount = modCount;

next, previous, remove, add, set 方法所做的第一件事,就是先判断该列表是否发生了结构性修改:

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

该方法实际上就是比较此时修改次数与迭代器创建之初的所记录的修改次数是否相等。

而调用迭代器所具备的修改方法,迭代器所记录的修改次数会同步更新,此时则不会抛出并发修改异常。

posted @ 2023-08-05 17:25  Zebt  阅读(51)  评论(0)    收藏  举报