Hello,大家好:
在上一篇博文中我们讲解了设计模式中的迭代器模式,这一篇文章,咱们来聊聊JDK源码中是如何去实现迭代器模式的。如果对迭代器模式不清楚的同学,请查看这篇文章超详细-设计模式之迭代器模式。

在Java中存储数据的数据结构有很多种,例如Map、数组、列表等等。每种数据结构的遍历方式都不相同,对于使用者来说,我肯定希望能在不知道每种数据结构内部的存储细节的情况下,对每种容器完成遍历。于是Java实现了迭代器模式,定义一种通用的遍历元素的方法,由每种数据结构去实现这些遍历方法。下面我们将从源码的角度分析Java中迭代器模式的实现。

在讲解源码之前,抛出一个小坑,不知道小伙伴们在使用迭代器循环列表时,对列表进行删除操作,会抛出ConcurrentModificationException异常,但是从迭代器中删除元素,也可以删除底层集合中的元素,并且不会抛出异常,示例代码如下:

List<Integer> list= new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
    iterator.remove();//不会抛出异常
    //list.remove(0); //会抛出异常
}

待会通过大家阅读源码自会有答案。

Iterator接口:
JDK对它的定义为:一个集合的迭代器,迭代器在java集合框架中替代了Enumeration,迭代器和Enumeration由两个不同之处:

  1. 迭代器在迭代期间可以从集合中移除元素。
  2. 方法名得到了改进,Enumeration的方法名称都比较长。

注:Enumeration接口中定义了一些方法,通过这些方法可以枚举(一次获得一个)对象集合中的元素。这种传统接口已被迭代器取代,虽然Enumeration 还未被遗弃,但在现代代码中已经被很少使用了。尽管如此,它还是使用在诸如Vector和Properties这些传统类所定义的方法中,除此之外,还用在一些API类,并且在应用程序中也广泛被使用。今天它不是重点。

Iterator方法介绍:

  1. boolean hasNext():如果迭代具有更多的元素,则返回true 。 (换句话说,如果next()返回一个元素而不是抛出一个异常,则返回true )
  2. E next():返回迭代中的下一个元素。
  3. void remove():从底层集合中删除此迭代器返回的最后一个元素(可选操作)。 此方法只能调用一次next() 。 如果底层集合在迭代过程中以任何方式进行修改而不是通过调用此方法,则迭代器的行为是未指定的。
  4. void forEachRemaining(Consumer<? super E> action):对每个剩余元素执行给定的操作,直到所有元素都被处理或动作引发异常。 如果指定了该顺序,则按迭代的顺序执行操作。 动作抛出的异常被转发给呼叫者。

Iterable(java.lang.Iterable) 接口:
是Java集合的顶级接口之一。Collection接口继承Iterable,所以Collection的所有子类也实现了Iterable接口。该接口的核心方法是:Iterator< T > iterator();该方法返回一个Iterator类,用以迭代元素。

上面提到Java集合的所有子类都实现了Iterator接口的方法,用以返回一个迭代器遍历元素,我们以ArrayList的源码为例,来详细的看一下具体存储元素的容器类是如何维护一个迭代器的。下面是ArrayList类中实现Iterable接口的iterator()方法源码:

//该方法是对Iterable接口中定义的方法的实现。返回Itr类
public Iterator<E> iterator() {
        return new Itr();
    }

以下是Itr源码,Itr类是ArrayList中的一个内部类,因为在迭代器迭代过程中,会访问ArrayList中存储的元素。所以将其定义为一个内部类。

private class Itr implements Iterator<E> {
        int cursor;       // 下一个元素的索引
        int lastRet = -1; // 返回迭代过程中最后一个元素的索引,如果没有返回-1
        int expectedModCount = modCount; //让预计修改次数等于修改次数

        Itr() {}
		//该方法判断是否还有下一个元素,只是判断当前游标是否等于容器的大小
        public boolean hasNext() {
            return cursor != size;
        }
		//该方法通过cursor++的方式进行迭代。lastRet指向迭代过程中最后一个元素。
        @SuppressWarnings("unchecked")
        public E next() {
        	//检查元素是否被修改,就是判断expectedModCount的值和modCount是否相等。
            checkForComodification();
            int i = cursor;//记录当前要迭代的位置
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
             //将当前游标+1
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }
		//从底层集合中移除lastRet指向的元素,详情看图说明
        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();
		
            try {
            	//其实是调用ArrayList自身的remove方法
                ArrayList.this.remove(lastRet);
                //让指针指向删除元素的位置
                cursor = lastRet;
                lastRet = -1;
                //让expectedModCount = modCount,所以在下一次迭代时,不会ConcurrentModificationException抛出异常。
                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();
            }
            //在遍历过程中,将elementData存储的元素传入给了Custom类的accept方法。
            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();
        }
    }

remove元素细节如下,假设现在调用next()方法,得到元素值为2,删除2号元素后,游标位置如下图所示:

IM 源代码 Java java iterator源码_迭代


上述操作代码如下:

List<Integer> list= new ArrayList<Integer>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        Iterator<Integer> iterator = list.iterator();
        while (iterator.hasNext()) {
            if(iterator.next() == 2) {
                iterator.remove();
            }
        }
        System.out.println("list.size="+list.size());

对forEachRemaining()方法使用代码如下:

List<Integer> list= new ArrayList<Integer>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        Iterator<Integer> iterator = list.iterator();
        iterator.forEachRemaining(new Consumer<Integer>() {
            public void accept(Integer t) {
                System.out.println(t);
            };
        });

简单介绍一下Consumer接口:表示接受单个输入参数且不返回结果的操作。

阅读源码后,文章开头我们抛出的那个坑,就能够从checkForComodification()方法中得到答案,在使用迭代器模式进行遍历时,如果有线程向集合中添加或者删除元素,那么modcount的值便会修改,使得expectedModCount不等于modCount。从而抛出这个异常。之所以使用迭代器删除元素不会抛出这个异常,是因为迭代器删除元素后会重新让expectedModCount等于modCount,ArrayList添加和删除元素的过程请阅读ArrayList源码解析。

好,ArrayList对迭代器的实现原理到这儿就讲述的差不多啦,有兴趣的小伙伴可以再去看看其他集合类的源码。本文若有不足之处,还请大家多多指正。