后端---Java中Iterator(迭代器)原理分析

Java中Iterator(迭代器)的用法及其背后机制的探究

一.背景延伸 

在Java中遍历List时会用到Java提供的Iterator,Iterator十分好用,原因是:

迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构。迭代器通常被称为“轻量级”对象,因为创建它的代价小。

  Java中的Iterator功能比较简单,并且只能单向移动:

  (1) 使用方法iterator()要求容器返回一个Iterator。第一次调用Iterator的next()方法时,它返回序列的第一个元素。注意:iterator()方法是java.lang.Iterable接口,被Collection继承。

  (2) 使用next()获得序列中的下一个元素。

  (3) 使用hasNext()检查序列中是否还有元素。

  (4) 使用remove()将迭代器新返回的元素删除。

二.为什么使用迭代器?

因为最初的时候你用for循环遍历List,之后人家又要你遍历Set,但是for循环无法遍历Set,因为Set是无序的(无法get值),所以后面就统一用迭代器遍历集合了。

三.迭代器迭代顺序

HashMap按键的顺序,HashSet按hashCode的顺序。

四.Iterator的使用 

(一)Iterator在Collection接口中的使用。

   虽然Collection接口的相关类实现了get()方法,但将Iterator用在它们身上仍然是合适的,下面以ArrayList为例,讨论Iterator在Collection中的两中使用方法:

 1.配合while()循环实现遍历输出:

1 ArrayList list = new ArrayList();
2 //此处省略list的具体赋值过程
3 Iterator it = list.iterator();
4         while(it.hasNext()){
5             System.out.println(it.next());
6         }

while()中的判断条件it.hasNext()用于判断it中是否还有下一元素,有的话就继续循环,输出语句中的it.next()既可以使“指针”往后走一位,又能将当前的元素返回,用于输出。

不过上面的是在一般while()循环中的使用,我们还可以用for each 循环来代替Iterator,因为for each 本身就相当于一个迭代器了:

1 ArrayList list = new ArrayList();
2 //此处同样省略list的赋值过程
3 for(Object array:list){
4            System.out.println(array);
5 }

需要注意的是for each 不适合用来增删元素,如果单纯用来遍历元素,这种写法也许会更简洁一点。

(二)Iterator在Map接口中的使用:

下文用HashMap为例,讨论迭代器的两种主要使用方法。

1.与while()的结合

HashMap<K,V> myMap = new HashMap<K,V>();
//省略myMap的的赋值过程
Iterator<Map.Entry<K,V> it=myMap.entrySet().iterator();
while(it.hasNext()){
        System.out.println(it.next());
}

//如果想让输出更加格式化,可以自己重写toString()方法,由于toString方法的重写不是本文讨论的重点,所以暂且不讨论。

Map接口下的iterator应用方法与Collection中的略微有些不同,用到了entrySet()方法,entrySet()用来返回整个键—值对。

同样这里再贴出for each代替Iterator的用法:

1 HashMap<K,V> myMap=new HashMap<K,V>();
2 //省略myMap赋值过程
3 for(Object oj:myMap.entrySet()){
4             System.out.println(oj);
5 }

五.Iterator的原理 

 所有Iterator都最终实现接口Iterator,Iterator接口中包含三个基本方法,next(), hasNext(), remove(),其中对于List的遍历删除只能用Iterator的remove方法;JDK1.8中Iterator接口的源码如下:

public interface Iterator<E> {

    boolean hasNext();

    // JDK1.8的新特性,可以通过default在接口中写个方法的实现
    default void remove() {
        throw new UnsupportedOperationException("remove");
    }
    
    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
}

下面将基于ArrayList的Iterator的实现分析Iterator的原理(基于JDK1.8):

在ArrayList类中有个方法iterator(),此方法将返回一个iterator的实现,这里可以看出实现类叫Itr,通过其它源码可知,此类是AarryList的内部类,即ArryList的Iterator实现在ArrayList内部;
这时ArrayList返回一个Iterator迭代器的方法:

   public Iterator<E> iterator() {
        return new Itr();
    }

下面重点看下ArrayList中实现类Itr类的源码:

private class Itr implements Iterator<E> {
        /**
         * 下一个返回的位置
         */
        int cursor = 0;

        /**
         * 当前操作的位置
         */
        int lastRet = -1;

        /**
         * 类似版本号,检查List是否有更新
         */
        int expectedModCount = modCount;

        public boolean hasNext() {      // 判断是否有下一个元素
            return cursor != size();
        }

        public E next() {  // 返回下一个元素
            checkForComodification();
            try {
                int i = cursor;     // cursor记录的是下一个元素,所以调用next时将返回的是cursor对应的元素
                E next = get(i);    //  记录需要返回的元素
                lastRet = i;        // 记录当前元素
                cursor = i + 1;     // 记录下一个元素
                return next;
            } catch (IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }

        public void remove() {       // 移除元素
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();     //   检查是否有更改,remove或者add

            try {
                AbstractList.this.remove(lastRet);   // 删除当前元素  
                if (lastRet < cursor)                // 删除了之后指标减1
                    cursor--;
                lastRet = -1;                    
                expectedModCount = modCount;         // 保持版本号一致
            } catch (IndexOutOfBoundsException e) {
                throw new ConcurrentModificationException();
            }
        }

        final void checkForComodification() {    // 如果有更改则抛出ConcurrentModificationException异常
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

从上面代码中可以看出,对于Iterator的实现中主要有几个变量cursor,lastRest, expectedModCount三个变量,其中cursor将记录下一个位置,lastRet记录当前位置,expectedModCount记录没有修改的List的版本号。

问题:还记得说List中在iterator遍历的时候,不能随便添加和删除元素吗,这是为什么呢?

在iterator遍历的时候抛出异常都是checkForComodification作的,根本原因是modCout和expectedModCount不相等,导致抛出异常

那为啥会不相等呢?

可以看看ArrayList的add和remove方法,
 

è¿éåå¾çæè¿°

 add方法:

è¿éåå¾çæè¿°

从上面的代码中可以看出只要对ArrayList作了添加或删除操作都会增加modCount版本号,这样的意思是在迭代期间,会不断检查modCount和迭代器持有的expectedModCount两者是不是相等,如果不想的就抛出异常了。

这样在迭代器迭代期间不能对ArrayList作任何增删操作,但是可以通过iterator的remove作删除操作,从之前的代码可以看出,在iterator的remove()中有一行代码,expectedModCount = modCount; 这个赋值操作保证了iterator的remove是可用性的。

当然,iterator期间不能增删的根本原因是ArrayList遍历会不准,就像遍历数组的时候改变了数组的长度一样
 

参考博文:

https://blog.csdn.net/qq_36101933/article/details/82632137

https://www.cnblogs.com/TangMoon/p/7589082.html

https://blog.csdn.net/qq_36470686/article/details/85065545

猜你喜欢

转载自blog.csdn.net/weixin_42504145/article/details/85210723
今日推荐