ListIterator接口
文章目录
ListIterator接口时Iterator接口的子接口,故ListIterator不光具有Iterator中的next()、hasNext()、remove()方法之外,自己也有一些其他的高级功能。
在介绍ListIterator接口之前,首先简单回顾以下Iterator中的一些成员和方法。
- cursor:用于记录当前迭代器遍历到的位置,通过该属性访问元素,初始值为0
- lastRet:该属性用于记录上一次使用next()方法时遍历所在的位置,在调用remove方法后该属性值变回-1,初始值为-1
- hasNext():判断是否有下一个元素
- next():返回下一个元素
- 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,表示刚刚进行了删除操作。