前面将ArrayList的基本一些功能的源码都看了一遍,现在到了核心的迭代器方面了,在这之前可以看看极客学院关于Iterator的讲解这里面的东西,讲的详细,我在略微懂了一点之后,继续将源码看下去。
文章说ArrayList里面自己定义了一个内部类,于是我立马找到了它
/**
* An optimized version of AbstractList.Itr
*/
private class Itr implements Iterator<E> {
// Android-changed: Add "limit" field to detect end of iteration.
// The "limit" of this iterator. This is the size of the list at the time the
// iterator was created. Adding & removing elements will invalidate the iteration
// anyway (and cause next() to throw) so saving this value will guarantee that the
// value of hasNext() remains stable and won't flap between true and false when elements
// are added and removed from the list.
protected int limit = ArrayList.this.size;
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
public boolean hasNext() {
return cursor < limit;
}
@SuppressWarnings("unchecked")
public E next() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
int i = cursor;
if (i >= limit)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
limit--;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@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;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
首先,这个类实现了一个接口Iterator,而这个接口存在于java.util.Iterator中。里面总共有四个方法:
default void | forEachRemaining(Consumer<? super E> action) Performs the given action for each remaining element until all elements have been processed or the action throws an exception. |
abstract boolean | hasNext() Returns |
abstract E | next() Returns the next element in the iteration. |
default void | remove() Removes from the underlying collection the last element returned by this iterator (optional operation). |
除了第一个方法之外,其他三个方法是使用最多的,也是ArrayList里面这个内部类自己实现了的方法。这下我们知道后面有哪些重要的方法。这个类的注释已经解释了,是AbstractList.Itr的优化版本,那么关注的点就来了:优化了什么?以及三个方法的实现。首先我们看它定义了什么:
// Android-changed: Add "limit" field to detect end of iteration.
// The "limit" of this iterator. This is the size of the list at the time the
// iterator was created. Adding & removing elements will invalidate the iteration
// anyway (and cause next() to throw) so saving this value will guarantee that the
// value of hasNext() remains stable and won't flap between true and false when elements
// are added and removed from the list.
protected int limit = ArrayList.this.size;
定义了一个int类型的变量,在注释中给我们解释了,这是为Iteration增加了一个limit字段来检测迭代是否结束。这可能也算作是改进吧,我们得知的消息是limit记录了ArrayList的size大小。
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
随后就是正常的两个变量的定义了,以及之前多次见到过的modCount,果然没有猜错它的功能。
public boolean hasNext() {
return cursor < limit;
}
@SuppressWarnings("unchecked")
public E next() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
int i = cursor;
if (i >= limit)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
首先看hasNext方法的实现,直接判断当前指向的元素下标是否超过了ArrayList的size大小,如果没超过,返回true表明后续还有元素。返回false表示后面已经没有元素了。
再看next方法,返回的是一个Element对象。首先通过比较modCount,如果modCount发生了改变,说明调用next方法的时候,ArrayList里面的数据被改动了,不是最新的数据,于是抛出错误异常。然后,我们获取cursor的值,然后和limit进行比较,也就是和ArrayList的size比较,如果大于等于size,那么表示后面没有更多的元素了,于是抛出错误异常。第三个if判断也很容易理解,从抛出的异常也很容易理解,如果是数组越界的话就会抛出IndexOutof.....的异常。这里的异常是ConcurrentModificationException,和第一个if的效果是一样的。设想,如果前面第一重if检测完毕没问题之后刚好ArrayList里面的数组发生了变动突然变得很短,此时这个i也就是cursor已经很大了,超过了数组的length,根本就什么都取不出来。
在经历过三重if的检测过后,确保数据已经是最新的了,于是将数据取出来,cursor和lastRet都相应的增大进行挪动,从这里我们知道cursor永远比lastRet大1.
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
limit--;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
然后我们再看remove方法,首先是第一重if检测判断,为什么使用lastRet来比较,而不是使用cursor来比较?因为实际上的所有操作,都是操作lastRet指向的元素。可以这么理解cursor的关系,cursor是向导的作用,脏活累活都是lastRet去做,获取的时候也是获取的lastRet指向的元素,删除也是删除lastRet指向的元素。
第二重if检测就是我们常见的检测了,为了确保这个数据是最新的,没有改动过的。我们再看trycatch里面的代码块。首先调用ArrayList自身的remove操作去移除lastRet位置的元素,此时一直走到lastRet前面一个位置的cursor自然要退回来,于是lastRet的值赋给了cursor。然后lastRet就重新变成了一个负数,不指向任何的元素,直到cursor移动就又跟在了cursor后面。expectedModCount和modCount同步,因为ArrayList自己的remove操作会改变modCount的值。由于remove了一个元素,所以limit自己也要减小1.
为什么会捕获IndexOutOfBoundsException异常呢?原因很简单,就和之前的next方法一样,如果在我尝试remove的时候,ArrayList里面的数据发生了改动,也就是要移除的元素跑走了。你指着一个10长度的第8个位置,你正打算要把它remove掉,突然它缩短了,原先的10长度只剩下4长度了。而你任然还指着第8个位置要移除它,当然把你看作数组越界了。
从next方法和remove方法中我们发现了一个弊端,虽然它可以很好的避免在操作的时候ArrayList的数据突然变短,但如果ArrayList的数据突然变长呢?接着上面举的例子,当这个10长度变成了15长度,有5个元素被添加到了最前面,而你原先想要操作的第8个位置的元素,突然跑到了第13个位置。那么就不是我们想要的操作结果了。
@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;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
前面我们就说过Iterator有四个方法,有三个方法是需要实现的,而有一个forEachRemaining方法是已经实现了的。这个方法的作用官方也解释了,对剩余的元素进行指定的操作,传递的是一个Consumer。而这个Consumer是在API24才添加进来的,它的内部是这样的:
Public methods | |
---|---|
abstract void | accept(T t) Performs this operation on the given argument. |
default Consumer<T> | andThen(Consumer<? super T> after) Returns a composed |
而官方对于它的解释是这样的一句话:
Represents an operation that accepts a single input argument and returns no result. Unlike most other functional interfaces,
Consumer
is expected to operate via side-effects.This is a functional interface whose functional method is
accept(Object)
.
我们可以这样理解,consumer就是一个指令动作,它有可能只是单个动作”左“,也有可能是一系列的复杂动作”左右右“,所以它才是一种集合的方式。好了,我们知道consumer是啥东西之后就可以开始理解代码了。
首先是判断传递进来的参数是不是仅仅只有一个名字而没有内容,然后获取ArrayList数据的size,用i存储当前指针cursor指向的位置,然后将i和size进行对比,如果i大于等于size,则可以认为已经遍历了所有的数了,没有剩余需要操作的数了,因为所有的元素都指了个遍,相当于所有元素都有被操作到。
然后定义一个变量将ArrayList的数据赋值给它,蛋疼的是名字依然取得是一样的名字elemengData。然后就是和之前两个方法一样的再次检测了,然后进入了while循环,不断的给剩余的元素赋予指令,让它们按照指令动起来。
当所有的元素都被指过之后,cursor更新自己的位置,将其指在正确的位置上,同时也刷新小跟班lastRet的位置。当所有事情做完之后,再确认一下之前操作的时候是不是最新的数据,如果是最新的数据那就可以放心了,如果不是就直接抛出异常。
总结:
Itr这个内部类并不是最后的迭代器,它还有一个子类,定义在ArrayList内部。所以Itr一些不完善的地方很有可能在子类中得到修复。
除了hasNext方法,其他的方法核心代码其实只有那么几行,这些方法做的最多的就是确保当前操作的数据是最新的数据。
cursor和lastRet的关系很微妙,cursor一开始并没有给它赋值,而lastRet一开始就默认赋予了-1,-1就相当于lastRet的出生点。我们可以通过判断lastRet是否为-1从而判断它是否跟在了cursor后面。remove可以理解为lastRet和它指向的元素同归于尽了,然后lastRet就跑到了出生点去了。