Java Iterator(迭代器)小笔记

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<String> names = new ArrayList<>();
        String[] namesArr = {"小明","二蛋","joshua317"};
        names.addAll(Arrays.asList(namesArr));

        System.out.println(names);

        //获取迭代器
        Iterator<String> 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<String> names = new ArrayList<>();
        String[] namesArr = {"小明","二蛋","joshua317"};
        names.addAll(Arrays.asList(namesArr));

        System.out.println(names);

        //获取迭代器
        Iterator<String> 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<E> {

    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 Iterator<E> iterator() {
        return new Itr();
    }

然后看下ArrayList中实现类Itr类的源码

private class Itr implements Iterator<E> {
        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<String> names = new ArrayList<>();
        String[] namesArr = {"小明","二蛋","joshua317"};
        names.addAll(Arrays.asList(namesArr));

        //获取迭代器
        Iterator<String> 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博客
请先登录后发表评论
  • latest comments
  • 总共0条评论