上一篇文章我们从源码角度介绍了ArrayList中两个比较相似的方法,现在我们总结一下:
1)removeAll:获取两个集合的差集,例如:list.removeAll(c),就是执行此方法,list会移除包含c的元素
2)retainAll:获取两个集合的交集,例如:list.retainAll(c),就是执行此方法,list会移除不包含c的元素
这篇文章接下来会从源码角度去分析ArrayList的迭代器。
ArrayList当中有两个迭代器,一个是iterator,一个是listIterator,下面我们逐一进行分析:
一、iterator
我们知道Collection接口继承了Iterater,所以必须实现iterator方法,下面的就是这个方法的具体实现。
public Iterator<E> iterator() {
return new Itr();
}
这个方法非常的简单,直接创建了一个ArrayList的一个内部类Itr
private class Itr implements Iterator<E> {
Itr实现了Iterator接口,
1)Itr的几个重要的属性
第一个重要的属性:cursor游标
int cursor;
cursor是下一个元素的下标,这是Itr最重要的一个属性了,其实他就类似于一个游标
第二个重要属性:lastRet最后一个元素的下标
int lastRet = -1; // index of last element returned; -1 if no such
2)Itr重要的方法,主要实现Iterator
第一个方法:hasNext
public boolean hasNext() {
return cursor != size;
}
其中size是ArrayList中元素数量,此方法是迭代器中判断还有没有下一个元素,就是游标cursor是否移动到了最后,如果移动到了最后,则返回false,否则返回true
第二个方法: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];-----------------------❸
}
❶在迭代器中体现了一个机制,就是fast-fail机制,那就是在迭代时,如果有对ArrayList调用add(),remove(),set()等能改变元素的方法时,会立刻抛出ConcurrentModificationException异常,就是说在迭代的时候不能修改ArrayList
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
modCount我们在这篇文章已经介绍过了,只要修改ArrayList里面的元素,就会使modCount++.如果你用for循环来代替iterator,则没有fast-fail的机制。
接下来int i = cursor,把游标赋值给i局部变量。如果i>=size,则会抛出异常,所以利用迭代器时要用hasNext()和next()结合使用,否则就会出现cursor>=size,导致出现异常。
❷就是把游标cursor向后移动一位,❸就是把当前位置赋值给lastRet,然后返回当前元素。
所以总结一下next方法
1.判断在迭代时,是否有对ArrayList进行修改,如果修改则直接抛出异常,是通过modCount判断的。
2.把ArrayList底层数组赋值给elementData,
3.然后移动游标,
4.把当前位置的下标赋值给lastRet,然后返回当前元素
从next方法可以看出,游标的移动,lastRet的赋值都是在这个方法完成的
第三个方法: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();
}
}
所以迭代有一个重要的作用,就是在迭代时可以删除,但是必须调用next后才能调用remove方法,因为如果不调用next,lastRet就是默认值-1,直接调用remove则会抛出异常,仔细想想不难理解,Itr迭代是通过游标cursor获取的,游标不移动,我们就无法获取到集合里面的元素,就不知道删除哪一个元素
所以总结一下remove方法:
1.判断lastRet是否大于等于0,如果不是则抛出异常,就是没有迭代元素,也就不知道要删除哪一个元素了。
2.判断一下在调用remove方法时,是否ArrayList是否修改过。
3.从ArrayList删除下标lastRet的元素
4.将lastRet下标赋值给游标cursor,这句话的意思就是,删除了当前元素,游标要向前移动一位。
5.将lastRet=-1,调用remove一次,就不能在删除了,只有在调用next,才能在调用remove
Itr主要理解这些内容就可以了,我们要知道ArrayList的迭代有这些特性
1.进行迭代进行遍历元素,当多个线程修改ArrayList时,就拥有fast-fail机制,则普通的循环则没有这个特性
2.在迭代的时候可以调用remove方法删除当前元素,而普通的循环如果调用删除,则会出现错误。
二、ListIterator
ArrayList中有两个方法能够获取ListIterator.
public ListIterator<E> listIterator() {
return new ListItr(0);
}
public ListIterator<E> listIterator(int index) {
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("Index: "+index);
return new ListItr(index);
}
private class ListItr extends Itr implements ListIterator<E> {
ListIterator继承了Itr,我们知道Itr只能向后迭代,游标只能往后走,而ListIterator则在Itr基础上增强了cursor的功能,不但能够向后移动,而且也能向前移动,能够向前遍历了。
ListItr(int index) {
super();
cursor = index;
}
新创建时就设置了游标cursor=index.
ListIIterator相对Itr,新增了如下的方法:
第一个增强方法:hasPrevious
public boolean hasPrevious() {
return cursor != 0;
}
此方法和hasNext相对,判断游标是否移动到了首位。
第二个增强方法:nextIndex:获取当前游标的位置
public int nextIndex() {
return cursor;
}
第三个增强方法:previousIndex,获取游标的前一个下标
public int previousIndex() {
return cursor - 1;
}
第四个增强方法: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];
}
和next作用相似,所以hasPrevious和previous要结合使用
第五个增强方法:set,覆盖下标lastRet的元素
public void set(E e) {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.set(lastRet, e);
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
第六个增强方法:add
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();
}
}
从上面的方法我们可以总结ListIterator的作用
1.游标即可以向前移动,也可以向后移动
2.迭代的时候可以进行增删改。
通过上面的分析,可以看出ArrayList具有迭代器的功能,我们总结一下迭代器和普通的循环的区别
1)迭代器迭代的时候可以fast-fail机制,而普通方法则没有
2)迭代器可以在迭代的时候可以移除当前迭代的元素,ListIterator可以add,set,而普通循环时不能进行增删改。