一篇搞懂:Java集合中迭代器iterator的作用?
前置知识
这是Collection的父类 ,实现该接口的类可以进行foreach遍历,所以Collection体系的所有集合类都可以获取迭代器来进行遍历(都可以通过iterator()创建一个迭代器对象)
// Implementing this interface allows an object to be the target of the "for-each loop" statement. See For-each Loop
public interface Iterable<T> {
// 返回一个迭代器
Iterator<T> iterator();
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
default Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
}
迭代器接口
public interface Iterator<E> {
// 是否有下一个元素
boolean hasNext();
// 获得下一个元素
E next();
// 删除迭代的最后一个元素,每次next只能调用一次
default void remove() {
throw new UnsupportedOperationException("remove");
}
// 对剩余元素做相应处理,传入一个消费者
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
迭代器的使用
实现了Collection的类都可以获得迭代器
public class Demo{
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
// 获得迭代器
Iterator<Integer> iterator = list.iterator();
// 迭代器遍历
while(iterator.hasNext()) {
int value = iterator.next();
System.out.print(value + " ");
}
}
}
ArrayList实现的iterator
每次获取都是获取一个新的迭代器,这样做是为了不同的迭代器之间不会互相影响
// 外界获得迭代器的方法,这里可以看出每次获取都是获取一个新的迭代器
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
int cursor; // 下一个元素位置
int lastRet = -1; // 最近返回的数据,就是已经遍历过的最后一个元素位置,默认为-1
int expectedModCount = modCount; // 防止并发操作的关键,将对list的操作次数获取
Itr() {}
// 有没有下一个就是直接判断cursor是不是等于元素个数了
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;
// 判断一下index是否越界,也是为了防止在此过程中被并发修改
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
// 小于0说明还没开始遍历
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
// 删除当前遍历的最后一个元素
ArrayList.this.remove(lastRet);
// cursor回退一下,重新指向已经向前挪一位的元素
cursor = lastRet;
// 这里设置为-1,可以防止一次移动删除两次元素
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() {
// 因为更改后modCount会变化,可以判断出来
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
通过iterator的实现类Itr的源码可以发现,他对并发操作做了预防,每次获取一个迭代器时,都会保存当前集合的修改次数,每次操作时都会判断修改次数是不是发生了改变(集合自己进行修改操作时,都会进行modCount++),来判断是不是进行了并发操作(这里除了多线程下的并发,还有一种就是在迭代器代码块中调用删除方法,也会出现并发修改异常)
这种判断并发出错直接抛出错误的思想就是fail-fast
增强for循环
for(Object obj:objs){}
算是一种简单遍历写法,算是一种语法糖,查看一下字节码
public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(1);
arrayList.add(2);
arrayList.add(3);
arrayList.add(4);
for (Integer integer : arrayList) {
System.out.println(integer);
}
}
对应的一部分字节码
ALOAD 1
INVOKEVIRTUAL java/util/ArrayList.iterator ()Ljava/util/Iterator;
ASTORE 2
L6
FRAME APPEND [java/util/ArrayList java/util/Iterator]
ALOAD 2
INVOKEINTERFACE java/util/Iterator.hasNext ()Z (itf)
IFEQ L7
ALOAD 2
INVOKEINTERFACE java/util/Iterator.next ()Ljava/lang/Object; (itf)
CHECKCAST java/lang/Integer
ASTORE 3
L8
可以发现它在字节码层面还是调用了获取迭代器然后执行迭代器循环的操作来遍历的!
总结:Java迭代器的作用
将遍历操作与底层数据结构分离,例如从ArrayList数组遍历,修改为LinkedArrayList链表,他俩的遍历操作完全不同