在阅读linkedList源码时,看到大量的ListIterator,那就写一篇来专门说说它吧。
继承实现关系很简单,那也顺便说说Iterator吧。
Iterator就是迭代器,常见于集合操作,而ListIterator就像它名字组成传达出来的意思一样,专供List用的。
初始化的时候:
Iterator it = List.Iterator();
指针其实就是指向List中的第一个元素,是不是觉得这句有点废话,等会儿讲ListIterator就知道是要对比两者差异了。
2、ArrayList中的实现Iterator本身是个接口,那我们就用ArrayList中的内部实现类来加以说明吧:
private class Itr implements Iterator {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
Itr() {}
// hasNext()
// next()
// remove()
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
首先,看到其中有几个主要字段:
(1)cursor就是下个要返回元素的索引,默认是0,即表示:初始化的时候,默认返回的是第一个元素;
(2)lastRet上一个返回的元素索引,默认是-1,这不难理解;
(3)expectedModCount预期修改次数;
(4)modCount实际修改次数,这俩不一样的时候就会抛出ConcurrentModificationException。
(5)hasNext() public boolean hasNext() {
return cursor != size;
}
没什么特别的,就是判断下一个要访问的元素索引是否等于集合长度,等于说明已经结束了(返回false),即表示没有下一个元素了。
注意它并没有移动指针。
这个可以稍微注意下,
@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];
}
一来就检查修改次数有没有问题;
然后检查索引值是不是超过集合大小了,即越界了;
接着把当前集合所有元素拿到,就是一个数组,看看要访问的元素索引是不是超出数组了,然后让指针指向下一个元素,并将当前元素返回。
相比hasNext(),next()会移动指针。
一般跟next()结合使用,且删除的是next()返回的那个元素,它是唯一一个在迭代过程中删除元素却又安全的方法,但是每次迭代只能调用一次(不然就会抛出异常),后面会说为什么。
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();
}
}
从第一个if判断得知,如果我们在对迭代器进行remove()操作时,必须确保lastReturn不为-1,也就是至少要执行一次next(),因为初始化的时候,默认lastRet是-1,将会抛出IllegalStateException异常:
while(iterator.hasNext()){
iterator.remove();
Integer integer = iterator.next();
if(integer.intValue() == 5){
iterator.remove();
}
}
下面这种就不会报错:
int count = 0;
while(iterator.hasNext()){
if(count > 0){
iterator.remove();
}
Integer integer = iterator.next();
if(integer.intValue() == 5){
iterator.remove();
}
}
好了,接着往下看,然后就是检查修改次数有没有问题;
然后是又一个可能抛出ConcurrentModificationException异常的try块,第一句就是调用ArrayList的删除指定索引位置元素的方法,看到是带有index的,那么这个lastRet刚开始我们分析了,因为先执行了next(),所以它至少是0,结合后两句我们发现,它把自己的0赋给了cursor,然后还把自己置为-1了,换句话说,就是每次remove的其实都是第一个元素,可以通过一段代码验证下:
我们看到arrayIntegerList的第一个元素(1)没了。
顺便这里提个平时不太注意的点,我们看到经过第一次遍历,元素变少的不止arrayIntegerList,还有followArrayList,Why,我没有操作followArrayList,为什么它也变少了,注意,我们操作的迭代器是arrayIntegerList,而我们是直接把arrayIntegerList赋给了followArrayList(Java语言中,对象赋值实际上是同一个对象具有两个不同的名字,因为它们都是同一个地址值。),我们看到下方圈出来的,两者hashcode一样,都是ArrayList@537,所以改了arrayIntegerList,followArrayList也会受影响。
而initialArrayList跟arrayIntegerList没有任何关系,所以无论迭代多少次,都没有变化。
最后,回到remove()源码try块里的最后一句,非常关键,这也是为什么不能在ArrayList的遍历里调用它的remove(),却可以在Iterator的遍历里调用其remove(),因为它相比ArrayList的那个只修改了modCount,却没修改expectedMODCount,它做了,
expectedModCount = modCount;
这么一个操作,所以就不会像前者那样抛出ConcurrentModificationException。
当然,这在单线程下没有问题,但如果想在多线程里整,还是要用java.util.concurrent包下的类,或者加一下其他可以确保线程安全的手段才行。
相比Iterator,更加强大了,但是呢,像名字里的构成一样,它只能用在各种List的操作,至于使用呢,就是调用.listIterator()方法,产生一个指向List的ListIterator,还可以调用listIterator(n)创建一个从索引n开始的ListIterator。
值得一提的是,cursor并不指向元素,而是元素之前,指向的是两个元素之间:
我们依然用ArrayList的源码来分析:
private class ListItr extends Itr implements ListIterator(1)hasPrevious(){ ListItr(int index) { super(); cursor = index; } // hasPrevious() // nextIndex() // previousIndex() // previous() // set() // add() }
public boolean hasPrevious() {
return cursor != 0;
}
我们看到它就一个表达式,为什么这么写,因为这个双向链表只有头节点没有前置节点,逆序遍历的时候,。
(2)nextIndex() public int nextIndex() {
return cursor;
}
(3)previousIndex()
public int previousIndex() {
return cursor - 1;
}
(4)previous()
@SuppressWarnings("unchecked")
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];
}
跟next()有点像,注意区分,
一来也是先检查expectedModCount跟modCount是否一致;
然后用个临时变量i,获取游标前移一位,如果小于0,说明当前已经是第一个元素了,那么就会抛出NoSuchElementException异常;
然后获取元素数组,如果值比数组长度还大,那就有问题了;
经过上述检查后确定没问题,才确实把游标前移,返回该元素并修改上一返回元素索引。
public void set(E e) {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.set(lastRet, e);
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
跟我们前面说的remove()有相似之处,如果lastRet小于0,那就是第一次遍历的时候,因为刚初始化,lastRet这时还是-1;
然后就是调用ArrayList自己的set了,检查一下索引的合法性,获取索引位置上的元素,把新元素换上去,返回旧元素的值。
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();
}
}
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
添加呢,拿到当前游标位置,调用ArrayList自己的add,检查索引的合法性,数组扩容,同时modCount增加了,数组拷贝过去,然后设置好相应位置上的新元素,size增加。
完成后回到ListIterator的add(),游标向前移一位,lastRet被置为-1,expectedModCount也被设置成跟modCount一样的值,还是那句话,就是因为这句,才能让iterator在遍历时增加、删除元素,而不至于抛出ConcurrentModificationException异常。



