本文为joshua317原创文章,转载请注明:转载自joshua317博客 Java Iterator(迭代器)小笔记 - joshua317的博客
Java Iterator(迭代器)不是一个集合,它是一种用于访问集合的方法,可用于迭代 ArrayList和 HashSet 等集合。Iterator 是 Java 迭代器最简单的实现,ListIterator 是 Collection API 中的接口, 它扩展了 Iterator 接口。
迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构。迭代器通常被称为“轻量级”对象,因为创建它的代价小。
迭代器 it 的两个基本操作是 next 、hasNext 和 remove。
Java中的Iterator功能比较简单,并且只能单向移动:
使用方法iterator()要求容器返回一个Iterator。 注意:iterator()方法是java.lang.Iterable接口,被Collection继承。
调用 it.next() 会返回迭代器的下一个元素,并且更新迭代器的状态。
调用 it.hasNext() 用于检测集合中是否还有元素。
调用 it.remove() 将迭代器返回的元素删除。
Iterator 类位于 java.util 包中,使用前需要引入它,语法格式如下:
import java.util.Iterator; // 引入 Iterator 类
1.获取一个迭代器并循环集合元素
集合想获取一个迭代器可以使用 iterator() 方法
package com.joshua317;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
public class Main {
public static void main(String[] args) {
//创建集合
ArrayList names = new ArrayList<>();
String[] namesArr = {"小明","二蛋","joshua317"};
names.addAll(Arrays.asList(namesArr));
System.out.println(names);
//获取迭代器
Iterator iterator = names.iterator();
//遍历集合元素,让迭代器 it 逐个返回集合中所有元素最简单的方法是使用 while 循环
while (iterator.hasNext()) {
String name = iterator.next();
System.out.println(name);
}
}
}
2.删除集合里面的元素
要删除集合中的元素可以使用 remove() 方法。
package com.joshua317;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
public class Main {
public static void main(String[] args) {
//创建集合
ArrayList names = new ArrayList<>();
String[] namesArr = {"小明","二蛋","joshua317"};
names.addAll(Arrays.asList(namesArr));
System.out.println(names);
//获取迭代器
Iterator iterator = names.iterator();
//遍历集合元素,让迭代器 it 逐个返回集合中所有元素最简单的方法是使用 while 循环
while (iterator.hasNext()) {
String name = iterator.next();
//删除集合里面字符串为"joshua317"的元素
if (name.equals("joshua317")) {
iterator.remove();
}
}
}
}
3.扩展-迭代器的原理
所有迭代器都最终实现接口Iterator,Iterator接口中包含三个基本方法,next(), hasNext(), remove(),其中对于List的遍历删除只能用Iterator的remove方法;JDK1.8中Iterator接口的源码如下:
package java.util; import java.util.function.Consumer; public interface Iterator{ boolean hasNext(); E next(); default void remove() { throw new UnsupportedOperationException("remove"); } default void forEachRemaining(Consumer super E> action) { Objects.requireNonNull(action); while (hasNext()) action.accept(next()); } }
接下来,我们基于ArrayList的Iterator的实现分析terator的原理(基于JDK1.8)
在ArrayList类中有个方法iterator(),此方法将返回一个iterator的实现,这里可以看出实现类叫Itr,通过其它源码可知,此类是AarryList的内部类,即ArryList的Iterator实现在ArrayList内部;
public Iteratoriterator() { return new Itr(); }
然后看下ArrayList中实现类Itr类的源码
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() {} public boolean hasNext() { return cursor != size; } @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]; } 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(); } } @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(); } }
从上面代码中可以看出,对于Iterator的实现中主要有几个变量cursor,lastRest, expectedModCount三个变量,其中cursor将记录下一个位置,lastRet记录当前位置,expectedModCount记录没有修改的List的版本号。
问题抛出:List中在iterator遍历的时候,为什么不能随便添加和删除元素吗?
在iterator遍历的时候抛出异常都是checkForComodification作的,根本原因是modCout和expectedModCount不相等,导致抛出异常。
我们来看下ArrayList的add和remove方法
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
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;
}
从代码中可以看出只要对ArrayList作了添加或删除操作都会增加modCount版本号,这样在迭代期间,会不断检查modCount和迭代器持有的expectedModCount两者是否相等,如果不相等就抛出异常了。
关于modCount,API解释如下: The number of times this list has been structurally modified. Structural modifications are those that change the size of the list, or otherwise perturb it in such a fashion that iterations in progress may yield incorrect results. 也就是说,modCount记录修改此列表的次数:包括改变列表的结构,改变列表的大小,打乱列表的顺序等使正在进行迭代产生错误的结果。 注意:仅仅设置元素的值并不是结构的修改 我们知道的是ArrayList是线程不安全的,如果在使用迭代器的过程中有其他的线程修改了List就会抛出ConcurrentModificationException
package com.joshua317;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
public class Main {
public static void main(String[] args) {
//创建集合
ArrayList names = new ArrayList<>();
String[] namesArr = {"小明","二蛋","joshua317"};
names.addAll(Arrays.asList(namesArr));
//获取迭代器
Iterator iterator = names.iterator();
//遍历集合元素,让迭代器 it 逐个返回集合中所有元素最简单的方法是使用 while 循环
while (iterator.hasNext()) {
String name = iterator.next();
names.add("小红");
}
}
}
Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911) at java.util.ArrayList$Itr.next(ArrayList.java:861) at com.joshua317.Main.main(Main.java:19)
虽然在迭代器迭代期间不能对ArrayList作任何增删操作,但是可以通过iterator的remove作删除操作,从之前的代码可以看出,在iterator的remove()中有一行代码,expectedModCount = modCount; 这个赋值操作保证了iterator的remove是可用性的。
当然,iterator期间不能增删的根本原因是ArrayList遍历会不准,就像遍历数组的时候改变了数组的长度一样。
本文为joshua317原创文章,转载请注明:转载自joshua317博客 Java Iterator(迭代器)小笔记 - joshua317的博客



