Java中ListIterator学习笔记(源码分析)

本文详细介绍了ListIterator接口的功能和实现原理,包括其与Iterator接口的区别、特有的方法如hasPrevious、previous等,以及如何在实际应用中使用这些方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ListIterator接口


ListIterator接口时Iterator接口的子接口,故ListIterator不光具有Iterator中的next()、hasNext()、remove()方法之外,自己也有一些其他的高级功能。

在介绍ListIterator接口之前,首先简单回顾以下Iterator中的一些成员和方法。

  1. cursor:用于记录当前迭代器遍历到的位置,通过该属性访问元素,初始值为0
  2. lastRet:该属性用于记录上一次使用next()方法时遍历所在的位置,在调用remove方法后该属性值变回-1,初始值为-1
  3. hasNext():判断是否有下一个元素
  4. next():返回下一个元素
  5. remove():移除上一次返回的元素

从之前对于Iterator实现类源码分析可知,Iterator迭代器只能实现往后遍历,也就是只能往一个方向进行遍历,类似于单链表,一旦前进了之后就无法后退。

而ListIterator迭代器却不同,它支持向前和向后遍历,同时除了原来的三个函数之外,它还可以修改元素,获取下标以及获取前面的元素。

由于ListIterator实现类是继承了Iterator实现类,故成员变量方面是一样的,代表的含义也是一样的,这里就不再赘述,主要对ListIterator中的所有方法进行源码分析,分析时借助ArrayList中的ListIterator迭代器。

类定义

首先查看类定义,可以看出这个类时private类型,继承了前面实现的Iterator实现类,同时也实现了ListIterator接口中的所有函数功能。

private class ListItr extends Itr implements ListIterator<E>

构造函数

在讲构造函数的时候,首先查看ListIterator类中的构造函数,代码如下所示。

ListItr(int index) {
            super();
            cursor = index;
        }

ListIterator实现类中只有上述这么一个有参构造函数,其中super()调用Iterator实现类中的构造方法(无参构造,内部为空),然后将cursor指针设置为参数index,也就是说明ListIterator实现类可以自定义遍历的起点。

接下来介绍以下如何获取ListIterator迭代器,有两个方法,一个是有参,一个是无参。理解起来也比较简单,有参的话就按照参数index来进行构造,如果无参则从起点开始即可,即index=0。源码如下所示。

public ListIterator<E> listIterator(int index) {
        if (index < 0 || index > size)
            throw new IndexOutOfBoundsException("Index: "+index);
        return new ListItr(index);
    }
    
    
    
public ListIterator<E> listIterator() {
        return new ListItr(0);
    }

hasPrevious()函数

如果以逆向遍历列表,列表迭代器有多个元素,则返回 true。(换句话说,如果 previous 返回一个元素而不是抛出异常,则返回 true)。

简单来说就是判断下一个要访问的元素之前是否还有元素。根据这个功能就简单来说,只要cursor指针不指向第一个元素,即cursor!= 0,就说明之前还有元素。

public boolean hasPrevious() {
            return cursor != 0;
        }

nextIndex()函数

返回对 next 的后续调用所返回元素的索引。(如果列表迭代器在列表的结尾,则返回列表的大小)。

public int nextIndex() {
            return cursor;
        }

从源码中可以看出,其实就是返回cursor的值即可。

previousIndex()函数

返回对 previous 的后续调用所返回元素的索引。(如果列表迭代器在列表的开始,则返回 -1)。

public int previousIndex() {
            return cursor - 1;
        }

从源码上来看,其实就是返回上一次调用的元素的下标,如果迭代器在列表的开始,则返回-1即可。

previous()函数

返回列表中的前一个元素。

可以重复调用此方法来迭代列表,或混合调用 next 来前后移动(注意交替调用 next 和 previous 将重复返回相同的元素)。

public E previous() {
            checkForComodification();
            int i = cursor - 1;
            if (i < 0)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i;
            return (E) elementData[lastRet = i];
        }

从源码上来看,因为cursor指向下一个要访问的元素,那么定义一个i指向cursor前面一个元素,其实也就是刚刚访问过的元素。之后还需要判断i 的合法性,如果cursor位于列表开始部分,则i=-1,属于不合法数据,则会抛出一个异常,之后就是返回i位置的元素,然后cursor指向i(后退一个),lastRet指向i(上一个被操作的数据),也就代表着执行previous之后,lastRet和cursor值相同,且cursor会在原来的基础上后退一格。

set(E e)函数

用指定元素替换 next 或 previous 返回的最后一个元素(可选操作)。

只有在最后一次调用 next 或 previous 后既没有调用 ListIterator.remove 也没有调用 ListIterator.add 时才可以进行该调用。

public void set(E e) {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.set(lastRet, e);
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

从源码和功能可以看出,该函数主要是对lastRet指向的元素进行修改,源码中主要是先判断合法性并检测对应的异常,重置部分主要是调用ArrayList中的set函数。

add(E e)函数

将指定的元素插入列表(可选操作)。

该元素直接插入到 next 返回的下一个元素的前面(如果有),或者 previous 返回的下一个元素之后(如果有);如果列表没有元素,那么新元素就成为列表中的唯一元素。新元素被插入到隐式光标前:不影响对 next 的后续调用,并且对 previous 的后续调用会返回此新元素(此调用把调用 nextIndex 或 previousIndex 所返回的值增加 1)。

public void add(E e) {
            checkForComodification();

            try {
                int i = cursor;
                ArrayList.this.add(i, e);
                cursor = i + 1;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

从源码中可以看出在检测异常之后,插入元素的位置在cursor当前所指向的元素的后面,故先定义一个变量i和cursor指向同一个元素,然后在位置i插入一个元素,此时cursor和i都指向新插入的元素,之后更新cursor为i+1即可完成插入,使得cursor指向插入之前所对应的元素,同时lastRet置为-1。

继承Iterator中的方法

ListIterator中关于hasNext()、next()、remove()的函数都是直接继承的Iterator中的方法,功能都是一样的,没有重写,这里参考前面关于Iterator中源码分析即可。

hasNext()函数

通过查看ArrayList中的源码得知,hasNext()的源码如下所示:

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

通过上述代码可以得知,判断方法也较为简单,其中size为当前集合的数据长度,将集合中的元素当作数组来考虑的话,那么对于一个长度为size的数组,有效范围就是0到size-1,由于cursor永远指向下一个元素的位置,故当cursor等于size的时候,说明之前访问的元素已经是最后一个元素了,那么此时就没有下一个元素了。

next()函数

通过查看ArrayList中的源码得知,next()的源码如下所示:

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];
        }

首先还是进行异常判断,将cursor使用变量i进行保存,如果i越界了,就需要抛出异常,之后就是前面提到的,返回cursor当前指向的元素,然后将lastRet设置为cursor的值,最后将cursor的值加一,也就是指向下一个元素。上述代码执行过程和原理解释部分顺序不太相同,但是实质是一样的。

remove()函数

通过查看ArrayList中的源码得知,remove()的源码如下所示:

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();
            }
        }

通过查看源码得知,程序一开始就会判断lastRet的合法性,由于lastRet初始值为-1,从这里也可以看出,至少要执行一次next()函数才能执行remove()函数,否则会抛出异常。

在判断了lastRet的合法性之后,接下来就是直接删除lastRet指向的元素,这个元素也就是前面刚刚访问过的元素。删除之后由于各个元素的位置会发生变化,所以cursor需要后退一格,这里使用cursor = lastRet来让cursor回退。同时将lastRet置为-1,表示刚刚进行了删除操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值