iterator接口源码分析(ArrayList中的实现),iteratorarraylist
iterator接口源码分析(ArrayList中的实现),iteratorarraylist
iterator是一个迭代器接口,它里面主要有:
boolean hasNext();
E next();
这两个方法,第一个方法表示迭代器含有更多元素则返回true;否则返回false;
第二个方法是返回迭代器的下一个元素;
其中还有两个实现方法:
default void remove() {
throw new UnsupportedOperationException("remove");
}
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
接下来我们来看看在不同集合类中是如何去实现迭代器的:
1.ArrayList中的实现方式:
public Iterator<E> iterator() {
return new Itr();
}
我们可以看到在这里使用了内部类:
private class Itr implements Iterator<E> {
int cursor; // 返回下一个元素的索引
int lastRet = -1; // 返回最后一个元素的索引,如果没有就这样
int expectedModCount = modCount;
Itr() {}
接下来我们看一下hasNext():
public boolean hasNext() {
return cursor != size;
}
在这里使用了cursor与size比较返回的boolean值; @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];
}
这里调用的方法:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
这个方法是什么作用呢?就是Fail-Fast机制,就是expectedModCount预期修改的次数,modCount就是实际修改的次数;
在这迭代器中,首先定义了一个expectedModCount=modCount默认值是0;这样通过上面的checkForComodification()方法就可以判断list集合在迭代过程中是否被其他线程修改过,这里的修改就是集合的增删改查;因为这些操作都能引起modCount数值加1的操作,这样就使得迭代失败了。
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
这一段需要说一下:
第一:为什么我们要创建一个elementData数组,而不是直接ArrayList.this.elementData[lastRet = i]取值?
这里是为了减少寻址次数,如果不定义,那么判断的时候寻址一次,取值的时候又要寻址一次;
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();
}
}
迭代器的移除方法,这里先不说实现,先问一个问题,既然迭代器也有移除,list本身也有移除,那么他们之间有什么不同呢?
那我们接下来来看一下ArrayList下的remove方法:
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
这里有个rangeCheck(index)方法,它是干什么的?
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
我们能够看到,它是当前集合坐标大于等于集合size的时候抛出下标越界异常;接着我们往下看,这里有个我们熟悉的变量 modCount++;在迭代器中我们有与之判断的变量:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
就是这个方法,如果使用ArrayList的remove方法移除的话那么 modCount就会加1,这样迭代器就会报错,所以说在使用迭代器进行遍历的时候是不能使用移除,添加等功能的;E oldValue = elementData(index);
是要被移除的元素;
int numMoved = size - index - 1;
这个是计算被移除的元素坐标之后的长度,为什么要减1呢?由于坐标是从0开始的。
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null;
当移除的不是最后一个元素的时候,就进行数组复制,这里说一下详细的参数含义:
elementData:是原数组;
index+1:原数组复制的起始位置;
elementData:目标数组;
index;目的数组放置的起始位置;
numMoved:复制的长度;
执行完之后将原数组置位null;便于jvm回收;
接下来我们看下迭代器实现的移除方法,看看有什么不一样的。
首先不同的是ArrayList里面的remove需要传参:1)一种是传指针2)一种是传元素
然后我们看他们的内部实现:
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
可以看到,迭代器的remove首先调用了ArrayList的remove方法,也就是咱们上面说的那些,那这里哟偶什么不同呢?
1)这里没有进行modCount++;也不能这么说,其实还是有的,那就是调用ArrayList的remove时,在它里面进行的modCount++;
那既然这样,那迭代的时候移除元素,不一样会会使得checkForComodification();方法抛出异常吗?
那我们来看第二个不同:
2)它这里处理的很巧妙,就是最后再重新给expectedModCount赋值,这样就能一直保持一样了,这一点ArrayList中的remove方法却没有这个;
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
然后我们还看到一个问题,那就是另两个赋值是干嘛用的?
这就要结合迭代器的next来看了,也就是说:只有当迭代器限制性next之后,才能remove,否则remove会跑出异常,why?
我们来看一下开头的判断:
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
lastRet判断小于0就要抛出异常,而我们默认的值是-1;那么好吧肯定得获取到元素的索引吧,而且你也能看到在上面remove方法中lastRet又重新赋值了-1;这样就可以看出来如果不去执行其他的操作,而直接使用remove是不可能的。
我们再来看一下next方法,它里面有一句这样的赋值:
return (E) elementData[lastRet = i];
i是什么?就是cursor:下一个元素的索引,而在这里i却是当前的索引,因为在cursor+1之前,就已经赋值给i了,所以这里是当前的索引,也就是0;
到这里你是不是已经看出来上面的问题了,只有每次调用next方法获取当前的索引,你才能够将其移除。
还有一个forEachRemaining方法,这个就不说了,这是jdk1.8加入的一种Lamdba表达式。
到这里我们可以来做几个小例子来巩固一下:
1)看一下下面的程序会不会执行结果是什么?
public static void main(String[] args) {
ArrayList<String> a=new ArrayList<String>();
for(int j=0;j<5;j++){
a.add("a");
}
Iterator<String> iterator = a.iterator();
while(iterator.hasNext()){
iterator.remove();
}
System.out.println(a.size());
}
这里执行后会跑出异常:就是这里,我们没有获取当前的索引;
2)这个会怎么样
public static void main(String[] args) {
ArrayList<String> a=new ArrayList<String>();
for(int j=0;j<5;j++){
a.add("a");
}
for(int i=0;i<a.size();i++){
if("a".equals(a.get(i))){
a.remove(i);
}
}
System.out.println(a.size());
}
这个会正常运行的,但是结果却是存在问题的,这是因为你每次遍历的时候i都加了1,但是你移除之后,原来的元素的索引都产生了变化,即减了1,这样下一次再移除的时候就会从索引1开始,这样变成0的索引就无法删除了,这里考察了那个问题就是remove中的复制:
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
3)那增强for循环呢?
public static void main(String[] args) {
ArrayList<String> a=new ArrayList<String>();
for(int j=0;j<5;j++){
a.add("a");
}
for(String b:a){
if("a".equals(b)){
a.remove(b);
}
}
System.out.println(a.size());
}
这个一样是跑出异常的,
首先说一下,增强for循环其实就是迭代器的next方法,这个大哥断点你就可以看到,那么好了我们知道了它调用了迭代器的next方法,此方法里面有个 checkForComodification();校验,第一次是成功的进入了,然后调用ArrayList的remove方法,modCount++了,而ArrayList中的remove有没有最后 expectedModCount = modCount;进行赋值,因此会跑出异常:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
恩,这样就可以了,那有没有可以一边迭代一边修改的迭代器呢,有的,就是listIterator迭代器,看名字就知道它的作用是list集合,而不是适应所有的集合的。这个之后再说。相关文章
- 暂无相关文章
用户点评